cpsa-3.3.0: src/CPSA/DiffieHellman/Algebra.hs
-- Diffie-Hellman Algebra implementation
-- This module implements a version of Diffie-Hellman in which
-- exponents form a free Abelian group. It uses the basis elements as
-- atoms principle.
-- To support security goals, the message algebra has been augmented
-- with support for variables of sort node and pairs of integers. The
-- constructor D is used as N is taken for numbers in S-Expressions.
-- Copyright (c) 2009, 2014 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.
--------------------------------------------------------------------
-- The module implements a many-sorted algebra, but is used as an
-- order-sorted algebra. It exports a name, and the origin used to
-- generate variables.
-- The Diffie-Hellman Order-Sorted Signature is
-- Sorts: mesg, text, data, name, skey, akey,
-- string, base, expr, and expn
--
-- Subsorts: text, data, name, skey, akey,
-- base, expr < mesg and expn < expr
--
-- Operations:
-- cat : mesg X mesg -> mesg Pairing
-- enc : mesg X mesg -> mesg Encryption
-- hash : mesg -> mesg Hashing
-- string : mesg Tag constants
-- ltk : name X name -> skey Long term shared key
-- bltk : name X name -> skey Bidirectional long-term key
-- pubk : name -> akey Public key of principal
-- pubk : string X name -> akey Tagged public key of principal
-- invk : akey -> akey Inverse of asymmetric key
-- gen : base DH generator
-- exp : base X expr -> base Exponentiation
-- mul : expr X expr -> expr Group operation
-- rec : expr -> expr Group inverse
-- one : expr Group identity
--
-- Atoms: messages of sort text, data, name, skey, akey, and expn, and
-- messages of the form (exp (gen) x) where x is of sort expn.
-- A free Abelian group has a set of basis elements, and the sort expn
-- is the sort for basis elements. Limiting the atoms associated with
-- an exponent to basis elements is the basis elements as atoms
-- principle. This principle enables CPSA to correctly handle
-- origination assumptions.
-- Variables of sort string are forbidden.
-- The implementation exploits the isomorphism between order-sorted
-- algebras and many-sorted algebras by adding inclusion operations to
-- produce an equivalent Diffie-Hellman Many-Sorted Signature. There
-- is an inclusion operation for each subsort of mesg. Diffie-Hellman
-- exponents are handled specially using a canonical representation as
-- monomials.
-- Sorts: mesg, text, data, name, skey, akey,
-- string, base, expr, and expn
--
-- Operations:
-- cat : mesg X mesg -> mesg Pairing
-- enc : mesg X mesg -> mesg Encryption
-- hash : mesg -> mesg Hashing
-- string : mesg Tag constants
-- ltk : name X name -> skey Long term shared key
-- bltk : name X name -> skey Bidirectional long-term key
-- pubk : name -> akey Public key of principal
-- pubk : string X name -> akey Tagged public key of principal
-- invk : akey -> akey Inverse of asymmetric key
-- text : text -> mesg Sort text inclusion
-- data : data -> mesg Sort date inclusion
-- name : name -> mesg Sort name inclusion
-- skey : skey -> mesg Sort skey inclusion
-- akey : akey -> mesg Sort akey inclusion
-- base : base -> mesg Sort base inclusion
--
-- A message of sort expr, a monomial, is represented by a map from
-- identifiers to descriptions. A description is a pair consisting
-- of a flag saying if the variable is of sort expn or expr, and a
-- non-zero integer. For t of sort expr, the monomial associated
-- with t is
--
-- x1 ^ c1 * x2 ^ c2 * ... * xn ^ cn
--
-- for all xi in the domain of t and t(xi) = (_, ci).
-- In both algebras, invk(invk(t)) = t for all t of sort akey,
-- (exp h (one)) = h, (exp (exp h x) y) = (exp h (mul x y)), and
-- the Abelian group axioms hold.
{-# LANGUAGE MultiParamTypeClasses, CPP #-}
-- Fail on non-canonical terms with this is defined
-- #define CANONICAL
module CPSA.DiffieHellman.Algebra (name,
-- iUnify, iMatch,
origin)
where
import Control.Monad (foldM)
import qualified Data.List as L
import qualified Data.Set as S
import Data.Set (Set)
import qualified Data.Map as M
import Data.Map (Map)
import Data.Char (isDigit)
import qualified CPSA.Lib.CPSA as C
import CPSA.Lib.CPSA (SExpr(..), Pos, annotation)
{- Export iUnify and iMatch for GHCi for debugging
For this to work, you must install the package bytestring-handle from
Hackage and tell GHCi that it is not hidden on the command line or
within GHCi with:
:set -package bytestring-handle
-}
{--
import System.IO (Handle)
import Data.ByteString.Lazy.Char8 (pack)
import Data.ByteString.Handle
import Control.Exception (try) --}
{--
-- Debugging support
import System.IO.Unsafe
z :: Show a => a -> b -> b
z x y = unsafePerformIO (print x >> return y)
zz :: Show a => a -> a
zz x = z x x
zzz :: (Show a, Show b) => a -> b -> b
zzz x y = z (x,y) y
zn :: Show a => a -> Maybe b -> Maybe b
zn x Nothing = z x Nothing
zn _ y = y
zf :: Show a => a -> Bool -> Bool
zf x False = z x False
zf _ y = y
zt :: Show a => a -> Bool -> Bool
zt x True = z x True
zt _ y = y
--}
{--
stringHandle :: String -> IO Handle
stringHandle s = readHandle False (pack s)
stringPosHandle :: String -> IO C.PosHandle
stringPosHandle s =
do
h <- stringHandle s
C.posHandle "" h
stringLoad :: String -> IO [SExpr Pos]
stringLoad s =
do
h <- stringPosHandle s
loop h []
where
loop h xs =
do
x <- C.load h
case x of
Nothing ->
return $ reverse xs
Just x ->
loop h (x : xs)
sLoad :: String -> [[SExpr Pos]]
sLoad s =
[unsafePerformIO $ stringLoad s]
-- Test unification
iUnify :: String -> String -> String -> [Subst]
iUnify vars t t' =
iRun unify emptySubst vars t t'
-- Test matching
iMatch :: String -> String -> String -> [Env]
iMatch vars t t' =
iRun match emptyEnv vars t t'
iRun :: (Term -> Term -> (Gen, a) -> [(Gen, a)]) -> a ->
String -> String -> String -> [a]
iRun f mt vars t t' =
do
vars <- sLoad vars
[t] <- sLoad t
[t'] <- sLoad t'
(gen, vars) <- loadVars origin vars
t <- loadTerm vars t
t' <- loadTerm vars t'
(_, a) <- f t t' (gen, mt)
return a
gRun :: Gen -> Term -> a -> a
gRun (Gen n) t a =
foldVars f a t
where
f a t =
case varId t of
Id (m, _) | m >= n -> error ("Bad gen " ++ show n)
_ -> a
gMatch :: Term -> Term -> GenEnv -> [GenEnv]
gMatch t t' r@(g, _) = gRun g t' (match t t' r)
gUnify :: Term -> Term -> GenSubst -> [GenSubst]
gUnify t t' r@(g, _) = gRun g (F Cat [t, t']) (unify t t' r)
--}
name :: String
name = "diffie-hellman"
-- An identifier
newtype Id = Id (Integer, String) deriving Show
-- The integer distinguishes an identifier, the string is for printing.
instance Eq Id where
(Id (x, _)) == (Id (x', _)) = x == x'
instance Ord Id where
compare (Id (x, _)) (Id (x', _)) = compare x x'
idName :: Id -> String
idName (Id (_, name)) = name
-- Counter used for generating fresh identifiers.
newtype Gen = Gen (Integer) deriving (Show, Eq)
origin :: Gen
origin = Gen (0)
freshId :: Gen -> String -> (Gen, Id)
freshId (Gen (i)) name = (Gen (i + 1), Id (i, name))
cloneId :: Gen -> Id -> (Gen, Id)
cloneId gen x = freshId gen (idName x)
-- A term in an Abelian group is a map from identifiers to pairs of
-- bools and non-zero integers. The boolean is true if the variable
-- is a basis element.
type Coef = Int
type Desc = (Bool, Coef)
type Group = Map Id Desc
isGroupVar :: Group -> Bool
isGroupVar t =
M.size t == 1 && snd (head (M.elems t)) == 1
isBasisVar :: Group -> Bool
isBasisVar t =
M.size t == 1 && head (M.elems t) == (True, 1)
isExprVar :: Group -> Bool
isExprVar t =
M.size t == 1 && head (M.elems t) == (False, 1)
-- Assumes isGroupVar t == True or isBasisVar t == True!
getGroupVar :: Group -> Id
getGroupVar x = head $ M.keys x
-- Create group var as a basis element if be is true
groupVar :: Bool -> Id -> Term
groupVar be x = G $ M.singleton x (be, 1)
groupVarGroup :: Id -> Group
groupVarGroup x = M.singleton x (False, 1)
dMapCoef :: (Coef -> Coef) -> Desc -> Desc
dMapCoef f (be, c) = (be, f c)
invert :: Group -> Group
invert t = M.map (dMapCoef negate) t
expg :: Group -> Int -> Group
expg _ 0 = M.empty
expg t 1 = t
expg t n = M.map (dMapCoef (n *)) t
mul :: Group -> Group -> Group
mul t t' =
M.foldrWithKey f t' t -- Fold over the mappings in t
where
f x c t = -- Alter the mapping of
M.alter (g c) x t -- variable x in t
g c Nothing = -- Variable x not currently mapped
Just c -- so add a mapping
g (b, c) (Just (b', c')) -- Variable x maps to c'
| b /= b' = C.assertError "Algebra.mul: sort mismatch"
| c + c' == 0 = Nothing -- Delete the mapping
| otherwise = Just $ (b, c + c') -- Adjust the mapping
-- Why not replace M.assocs with M.toList elsewhere?
type Maplet = (Id, Desc)
mMapCoef :: (Coef -> Coef) -> Maplet -> Maplet
mMapCoef f (x, (be, c)) = (x, (be, f c))
mInverse :: [Maplet] -> [Maplet]
mInverse maplets = map (mMapCoef negate) maplets
isMapletNonzero :: Maplet -> Bool
isMapletNonzero (_, (_, c)) = c /= 0
group :: [Maplet] -> Group
group maplets =
M.fromList $ filter isMapletNonzero maplets
-- Function symbols--see foldVar to see the arity of each symbol.
data Symbol
= Text -- Text atom
| Data -- Another text-like atom
| Name -- Principal atom
| Skey -- Symmetric key atom
| Base -- Base of an exponentiated atom
| Ltk -- Long term shared symmetric key
| Bltk -- Bidirectional ltk
| Akey -- Asymmetric key atom
| Invk -- Inverse of asymmetric key
| Pubk -- Public asymmetric key of a principal
| Genr -- The generator constant for the group
| Exp -- Exponentiation function symbol
| Cat -- Term concatenation
| Enc -- Encryption
| Hash -- Hashing
deriving (Show, Eq, Ord, Enum, Bounded)
-- A Diffie-Hellman Algebra Term
data Term
= I !Id
| C !String
| F !Symbol ![Term]
| G !Group -- An exponent, an Abelian group
| D !Id -- Node variable
| P (Int, Int) -- Node constant
deriving Show
subNums :: Term -> Set Term
subNums t | isNum t = S.singleton t
subNums t@(F Exp _) = S.singleton (F Base [t])
subNums (F _ ts) = (S.unions (map subNums ts))
subNums _ = S.empty
calcIndicator :: Term -> Term -> Maybe Int
calcIndicator t v
| (not (isNum t) || not (isExpn v)) = Nothing
| (not (isVar v)) = Nothing
| otherwise = Just (ind t v)
where
ind t@(F Base _) v = case expCollapse t of
F Base [F Genr _] -> 0
F Base [I _] -> 0
F Base [F Exp [_, G m]] -> ind (G m) v
_ -> error ("Algebra.hs: expCollapse returned non-base element")
ind (G m) v = case M.lookup (extrVar v) m of
Nothing -> 0
Just (_, i) -> i
ind _ _ = 0
isExpn (G _) = True
isExpn _ = False
extrVar (G t) = head $ M.keys t
extrVar _ = error ("Algebra.hs: extrExpn called on a non-exponent")
equalTerm :: Term -> Term -> Bool
equalTerm (I x) (I y) = x == y
equalTerm (C c) (C c') = c == c'
equalTerm (G t) (G t') = t == t'
#if defined CANONICAL
equalTerm l@(F Invk [F Invk [t]]) t' = error ("EQ: " ++ show l)
equalTerm t l@(F Invk [F Invk [t']]) = error ("EQ: " ++ show l)
equalTerm l@(F Exp [t0, G t1]) t' | M.null t1 = error ("EQ: " ++ show l)
equalTerm t l@(F Exp [t0, G t1]) | M.null t1 = error ("EQ: " ++ show l)
equalTerm l@(F Exp [F Exp [t, G t0], G t1]) t' = error ("EQ: " ++ show l)
equalTerm t l@(F Exp [F Exp [t', G t0], G t1]) = error ("EQ: " ++ show l)
#else
equalTerm (F Invk [F Invk [t]]) t' = equalTerm t t'
equalTerm t (F Invk [F Invk [t']]) = equalTerm t t'
equalTerm (F Exp [t0, G t1]) t' | M.null t1 = equalTerm t0 t'
equalTerm t (F Exp [t0, G t1]) | M.null t1 = equalTerm t t0
equalTerm (F Exp [F Exp [t, G t0], G t1]) t' =
equalTerm (F Exp [t, G (mul t0 t1)]) t'
equalTerm t (F Exp [F Exp [t', G t0], G t1]) =
equalTerm t (F Exp [t', G (mul t0 t1)])
#endif
equalTerm (F Bltk [t0, t1]) (F Bltk [t0', t1']) =
(equalTerm t0 t0' && equalTerm t1 t1') ||
(equalTerm t0 t1' && equalTerm t1 t0')
equalTerm (F s u) (F s' u') =
s == s' && equalTermLists u u'
equalTerm (D x) (D y) = x == y
equalTerm (P p) (P p') = p == p'
equalTerm _ _ = False
forceVar :: Term -> Term
forceVar x | not $ termsWellFormed [x] =
error ("Algebra.hs:forceVar: terms not well formed" ++ show x)
forceVar (I x) = (I x)
forceVar (C x) = (C x) -- no variable!
forceVar (F Text y) = (F Text y)
forceVar (F Data y) = (F Data y)
forceVar (F Name y) = (F Name y)
forceVar (F Skey [(I x)]) = (F Skey [(I x)])
forceVar (F Skey [(F Ltk ns@(_:_))]) = forceVar (head ns)
forceVar (F Skey [(F Bltk ns@(_:_))]) = forceVar (head ns)
forceVar (F Base [(I x)]) = (F Base [(I x)])
forceVar (F Base [(F Exp ns@(_:(_:_)))]) = forceVar (ns !! 1) -- go to the exponent part
forceVar (F Base [F Genr []]) = (F Base [F Genr []]) -- no variable
forceVar (F Akey [(I x)]) = (F Akey [(I x)])
forceVar (F Akey [F Invk ns@(_:_)]) = forceVar (head ns)
forceVar (F Akey [F Pubk ns@(_:_)]) = forceVar (head ns)
forceVar (F _ ns@(_:_)) = forceVar (head ns) -- for Cat, Enc.
forceVar (G m) | not $ null (M.keys m) =
let (x, (be, _)) = head $ M.assocs m in
G $ M.fromList [(x, (be, 1))]
forceVar (D x) = (D x)
forceVar (P x) = (P x) -- no variable!
forceVar _ = error ("Algebra.hs:forceVar: something unexpected happened.")
equalTermLists :: [Term] -> [Term] -> Bool
equalTermLists [] [] = True
equalTermLists (t : u) (t' : u') =
equalTerm t t' && equalTermLists u u'
equalTermLists _ _ = False
instance Eq Term where
(==) = equalTerm
-- Term comparison respecting the axiom
compareTerm :: Term -> Term -> Ordering
compareTerm (I x) (I y) = compare x y
compareTerm (C c) (C c') = compare c c'
compareTerm (G t) (G t') = compare t t'
#if defined CANONICAL
compareTerm l@(F Invk [F Invk [t]]) t' = error ("COM: " ++ show l)
compareTerm t l@(F Invk [F Invk [t']]) = error ("COM: " ++ show l)
compareTerm l@(F Exp [t0, G t1]) t' | M.null t1 = error ("COM: " ++ show l)
compareTerm t l@(F Exp [t0, G t1]) | M.null t1 = error ("COM: " ++ show l)
compareTerm l@(F Exp [F Exp [t, G t0], G t1]) t' = error ("COM: " ++ show l)
compareTerm t l@(F Exp [F Exp [t', G t0], G t1]) = error ("COM: " ++ show l)
#else
compareTerm (F Invk [F Invk [t]]) t' = compareTerm t t'
compareTerm t (F Invk [F Invk [t']]) = compareTerm t t'
compareTerm (F Exp [t0, G t1]) t' | M.null t1 = compareTerm t0 t'
compareTerm t (F Exp [t0, G t1]) | M.null t1 = compareTerm t t0
compareTerm (F Exp [F Exp [t, G t0], G t1]) t' =
compareTerm (F Exp [t, G (mul t0 t1)]) t'
compareTerm t (F Exp [F Exp [t', G t0], G t1]) =
compareTerm t (F Exp [t', G (mul t0 t1)])
#endif
compareTerm (F Bltk [t0, t1]) (F Bltk [t0', t1']) =
if (compareTerm t0 t1 == GT) then (compareTerm (F Bltk [t1, t0]) (F Bltk [t0', t1']))
else (if (compareTerm t0' t1' == GT) then (compareTerm (F Bltk [t0,t1]) (F Bltk [t1', t0']))
else compareTermLists [t0, t1] [t0', t1'])
compareTerm (F s u) (F s' u') =
case compare s s' of
EQ -> compareTermLists u u'
o -> o
compareTerm (D x) (D y) = compare x y
compareTerm (P p) (P p') = compare p p'
compareTerm (I _) (C _) = LT
compareTerm (C _) (I _) = GT
compareTerm (I _) (F _ _) = LT
compareTerm (F _ _) (I _) = GT
compareTerm (I _) (G _) = LT
compareTerm (G _) (I _) = GT
compareTerm (I _) (D _) = LT
compareTerm (D _) (I _) = GT
compareTerm (I _) (P _) = LT
compareTerm (P _) (I _) = GT
compareTerm (C _) (F _ _) = LT
compareTerm (F _ _) (C _) = GT
compareTerm (C _) (G _) = LT
compareTerm (G _) (C _) = GT
compareTerm (C _) (D _) = LT
compareTerm (D _) (C _) = GT
compareTerm (C _) (P _) = LT
compareTerm (P _) (C _) = GT
compareTerm (F _ _) (G _) = LT
compareTerm (G _) (F _ _) = GT
compareTerm (F _ _) (D _) = LT
compareTerm (D _) (F _ _) = GT
compareTerm (F _ _) (P _) = LT
compareTerm (P _) (F _ _) = GT
compareTerm (G _) (D _) = LT
compareTerm (D _) (G _) = GT
compareTerm (G _) (P _) = LT
compareTerm (P _) (G _) = GT
compareTerm (D _) (P _) = LT
compareTerm (P _) (D _) = GT
compareTermLists :: [Term] -> [Term] -> Ordering
compareTermLists [] [] = EQ
compareTermLists (t : u) (t' : u') =
case compareTerm t t' of
EQ -> compareTermLists u u'
o -> o
compareTermLists [] _ = LT
compareTermLists _ [] = GT
instance Ord Term where
compare = compareTerm
-- Basic terms are introduced by defining a function used to decide if
-- a term is well-formed. The context of an occurrence of an identifier
-- determines its sort. A term that contains just an identifier and its
-- sort information is called a variable. The sort of a variable is
-- one of mesg, text, data, name, skey, and akey.
-- Terms that represent algebra variables.
isVar :: Term -> Bool
isVar (I _) = True -- Sort: mesg
isVar (F s [I _]) =
-- Sorts: text, data, name, skey, and akey
s == Text || s == Data || s == Name || s == Skey || s == Akey || s == Base
isVar (G t) = isGroupVar t
isVar _ = False
-- Note that isVar of (D _) is false.
isNodeVar :: Term -> Bool
isNodeVar (D _) = True
isNodeVar _ = False
-- Extract the identifier from a variable
varId :: Term -> Id
varId (I x) = x
varId (F Text [I x]) = x
varId (F Data [I x]) = x
varId (F Name [I x]) = x
varId (F Skey [I x]) = x
varId (F Akey [I x]) = x
varId (F Base [I x]) = x
varId (G t) | isGroupVar t = getGroupVar t
varId (D x) = x
varId _ = C.assertError "Algebra.varId: term not a variable with its sort"
isAcquiredVar :: Term -> Bool
isAcquiredVar (I _) = True
isAcquiredVar (F Base [I _]) = True
isAcquiredVar (G x) = isExprVar x
isAcquiredVar _ = False
-- A list of terms are well-formed if each one has the correct
-- structure and every occurrence of an identifier in a term has the
-- same sort. Variable environments are used to check the sort
-- condition. It maps an identifier to a variable that contains the
-- identifier.
-- termsWellFormed u ensures all terms in u use each identifier at the
-- same sort, and makes sure every term has the correct structure.
termsWellFormed :: [Term] -> Bool
termsWellFormed u =
loop emptyVarEnv u
where
loop _ [] = True
loop env (t : u) =
case termWellFormed env t of
Nothing -> False
Just env' -> loop env' u
newtype VarEnv = VarEnv (Map Id Term) deriving Show
emptyVarEnv :: VarEnv
emptyVarEnv = VarEnv M.empty
-- Check the structure and sort condition.
termWellFormed :: VarEnv -> Term -> Maybe VarEnv
termWellFormed xts t@(I x) =
extendVarEnv xts x t -- Mesg variable
termWellFormed xts t@(F Text [I x]) =
extendVarEnv xts x t -- Text variable
termWellFormed xts t@(F Data [I x]) =
extendVarEnv xts x t -- Data variable
termWellFormed xts t@(F Name [I x]) =
extendVarEnv xts x t -- Name variable
termWellFormed xts t@(F Skey [I x]) =
extendVarEnv xts x t -- Symmetric key variable
termWellFormed xts (F Skey [F Ltk [I x, I y]]) =
-- Long term shared symmetric key
doubleTermWellFormed xts (F Name [I x]) (F Name [I y])
termWellFormed xts (F Skey [F Bltk [I x, I y]]) =
-- Bidirectional Long term key
doubleTermWellFormed xts (F Name [I x]) (F Name [I y])
termWellFormed xts (F Akey [t]) = -- Asymmetric key terms
case t of
I x -> extendVarEnv xts x (F Akey [I x])
F Invk [I x] -> extendVarEnv xts x (F Akey [I x])
F Pubk [I x] -> extendVarEnv xts x (F Name [I x])
F Pubk [C _, I x] -> extendVarEnv xts x (F Name [I x])
F Invk [F Pubk [I x]] -> extendVarEnv xts x (F Name [I x])
F Invk [F Pubk [C _, I x]] -> extendVarEnv xts x (F Name [I x])
_ -> Nothing
termWellFormed xts (F Base [t]) =
baseVarEnv xts t
where
baseVarEnv xts t@(I x) =
extendVarEnv xts x (F Base [t])
baseVarEnv xts (F Genr []) =
Just xts
baseVarEnv xts (F Exp [t0, G t1]) =
do
xts <- baseVarEnv xts t0
termWellFormed xts (G t1)
baseVarEnv _ _ = Nothing
termWellFormed xts (G t) =
foldM expnVarEnv xts (M.assocs t)
where
expnVarEnv xts (x, (be, _)) =
extendVarEnv xts x (groupVar be x)
termWellFormed xts (C _) =
Just xts -- Tags
termWellFormed xts (F Cat [t0, t1]) =
doubleTermWellFormed xts t0 t1 -- Concatenation
termWellFormed xts (F Enc [t0, t1]) =
doubleTermWellFormed xts t0 t1 -- Encryption
termWellFormed xts (F Hash [t]) =
termWellFormed xts t
termWellFormed _ _ = Nothing
-- Extend when sorts agree
extendVarEnv :: VarEnv -> Id -> Term -> Maybe VarEnv
extendVarEnv (VarEnv env) x t =
case M.lookup x env of
Nothing -> Just $ VarEnv $ M.insert x t env
Just t' -> if t == t' then Just (VarEnv env) else Nothing
doubleTermWellFormed :: VarEnv -> Term -> Term -> Maybe VarEnv
doubleTermWellFormed xts t0 t1 =
do
xts <- termWellFormed xts t0
termWellFormed xts t1
-- Is the sort of the term a base sort?
isAtom :: Term -> Bool
isAtom (I _) = False
isAtom (C _) = False
isAtom (F s _) =
s == Text || s == Data || s == Name || s == Skey || s == Akey
isAtom (G x) = isBasisVar x
isAtom (D _) = False
isAtom (P _) = False
-- Is the term numeric?
isNum :: Term -> Bool
isNum (F Base _) = True
isNum (G _) = True
isNum _ = False
-- Does a variable occur in a term?
occursIn :: Term -> Term -> Bool
occursIn t t' | isVar t =
subterm (I $ varId t) t'
occursIn t _ =
error $ "Algebra.occursIn: Bad variable " ++ show t
subterm :: Term -> Term -> Bool
subterm t t' | t == t' =
True
subterm t (F _ u) =
any (subterm t) u
subterm (I x) (G t') =
M.member x t'
subterm (G t) (G t') | isBasisVar t = -- For constituent
M.member (getGroupVar t) t'
subterm _ _ = False
-- Fold f through a term applying it to each variable in the term.
foldVars :: (a -> Term -> a) -> a -> Term -> a
foldVars f acc t@(I _) = f acc t -- Mesg variable
foldVars f acc t@(F Text [I _]) = f acc t -- Text variable
foldVars f acc t@(F Data [I _]) = f acc t -- Data variable
foldVars f acc t@(F Name [I _]) = f acc t -- Name variable
foldVars f acc t@(F Skey [I _]) =
f acc t -- Symmetric key variable
foldVars f acc (F Skey [F Ltk [I x, I y]]) =
-- Long term shared symmetric key
f (f acc (F Name [I x])) (F Name [I y])
foldVars f acc (F Skey [F Bltk [I x, I y]]) =
-- Bidirectional Long term shared symmetric key
f (f acc (F Name [I x])) (F Name [I y])
foldVars f acc t@(F Akey [I _]) = f acc t -- Asymmetric keys
foldVars f acc (F Akey [F Invk [I x]]) = f acc (F Akey [I x])
foldVars f acc (F Akey [F Pubk [I x]]) = f acc (F Name [I x])
foldVars f acc (F Akey [F Pubk [C _, I x]]) = f acc (F Name [I x])
foldVars f acc (F Akey [F Invk [F Pubk [I x]]]) = f acc (F Name [I x])
foldVars f acc (F Akey [F Invk [F Pubk [C _, I x]]]) = f acc (F Name [I x])
foldVars f acc (F Base [t]) =
baseAddVars acc t
where
baseAddVars acc t@(I _) =
f acc (F Base [t])
baseAddVars acc (F Genr []) =
acc
baseAddVars acc (F Exp [t0, G t1]) =
foldVars f (baseAddVars acc t0) (G t1)
baseAddVars _ _ = C.assertError "Algebra.foldVars: Bad term"
foldVars f acc (G t) =
M.foldlWithKey expnAddVars acc t
where
expnAddVars acc x (be, _) =
f acc (groupVar be x)
foldVars _ acc (C _) = acc -- Tags
foldVars f acc (F Cat [t0, t1]) = -- Concatenation
foldVars f (foldVars f acc t0) t1
foldVars f acc (F Enc [t0, t1]) = -- Encryption
foldVars f (foldVars f acc t0) t1
foldVars f acc (F Hash [t]) = -- Hashing
foldVars f acc t
foldVars f acc t@(D _) = f acc t -- Node variable
foldVars _ _ t = error $ "Algebra.foldVars: Bad term " ++ show t
-- Fold f through a term applying it to each term that is carried by the term.
foldCarriedTerms :: (a -> Term -> a) -> a -> Term -> a
foldCarriedTerms f acc t@(F Cat [t0, t1]) = -- Concatenation
foldCarriedTerms f (foldCarriedTerms f (f acc t) t0) t1
foldCarriedTerms f acc t@(F Enc [t0, _]) = -- Encryption
foldCarriedTerms f (f acc t) t0
--foldCarriedTerms f acc t@(F Base [F Exp [_, t1]]) = -- Exponents
-- f (f acc t) t1
foldCarriedTerms f acc t = f acc t -- atoms and tags
-- Is a term carried by another term?
carriedBy :: Term -> Term -> Bool
carriedBy t t' =
t == t' ||
case t' of
F Cat [t0, t1] -> carriedBy t t0 || carriedBy t t1
F Enc [t0, _] -> carriedBy t t0
_ -> False
-- Is a term relevant to t held by another term t'?
relevantCarriedBy :: Set Term -> Term -> Term -> Bool
relevantCarriedBy avoid t t' =
relevant avoid t t' ||
case t' of
F Cat [t0, t1] -> relevantCarriedBy avoid t t0 || relevantCarriedBy avoid t t1
F Enc [t0, _] -> relevantCarriedBy avoid t t0
_ -> False
-- Is atom a constituent of a term? In other words, is atom among
-- the set of atoms required to construct the term?
constituent :: Term -> Term -> Bool
constituent t t' | isAtom t =
subterm t t'
constituent t _ =
error $ "Algebra.constituent: Bad atom " ++ show t
-- The key used to decrypt an encrypted term, otherwise Nothing.
decryptionKey :: Term -> Maybe Term
decryptionKey (F Enc [_, t]) = Just (inv t)
decryptionKey _ = Nothing
buildable :: Set Term -> Set Term -> Term -> Bool
buildable knowns unguessable term =
ba term
where
ba (I _) = True -- A mesg sorted variable is always buildable
-- ba (F Genr _) = True -- as is the generator
ba (C _) = True -- So is a tag
ba (F Cat [t0, t1]) =
ba t0 && ba t1
ba t@(F Enc [t0, t1]) =
S.member t knowns || ba t0 && ba t1
ba t@(F Hash [t1]) =
S.member t knowns || ba t1
ba t@(F Base _) = bb t
ba t = isAtom t && not (S.member t unguessable)
-- Buildable base term
bb (F Base [I _]) = True -- A variable of sort base is always buildable
bb (F Base [F Genr _]) = True -- and so is the generator
bb t@(F Base [F Exp [t0, G t1]]) =
any (\t2 -> (getBase t2 == t0) && relevant unguessable t2 t)
(S.toList knowns) || bb (F Base [t0]) && be t1
bb (_) = False
-- Buildable exponent
be exp =
all (flip notElem ids) $ M.keys exp
-- Exponent variables with origination assumptions
ids = getExpnOrigAssumptions unguessable
-- Known exponent without non-known variables
-- kns = map (stripExpn ids) (getExpns knowns)
getExpnOrigAssumptions :: Set Term -> [Id]
getExpnOrigAssumptions terms =
concatMap f $ S.elems terms
where
f (G t) = M.keys t -- This is an approximation
f _ = []
{--
stripExpn :: [Id] -> Group -> Group
stripExpn ids grp =
foldl f grp $ M.keys grp
where
f grp key
| notElem key ids = M.delete key grp
| otherwise = grp
getExpns :: Set Term -> [Group]
getExpns terms =
foldl f [M.empty] $ S.elems terms
where
f a (G t) = t:a
f a _ = a
termGen :: Group -> Gen
termGen t =
Gen (1 + maxl (map idInt (M.keys t)))
where
idInt (Id (i, _)) = i
maxl [] = 0
maxl xs = maximum xs
instOf :: Group -> Group -> Bool
instOf grp pat =
maybe False (const True) (match (G pat) (G grp) (gen, emptyEnv))
where
gen = mash (termGen grp) (termGen pat)
--}
-- Penetrator derivable predicate and checking for unrealized skeletons.
derivable :: Set Term -> Set Term -> Term -> Bool
derivable avoid sent term =
let (knowns, unknowns) = decompose sent avoid in
buildable knowns unknowns term
-- Compute the decomposition given some known terms and some unguessable
-- atoms. The code is quite tricky. It iterates until the known
-- terms don't change. The known terms ends up with all the
-- encryptions that are known.
decompose :: Set Term -> Set Term -> (Set Term, Set Term)
decompose knowns unguessable =
loop unguessable knowns S.empty []
where
loop unguessable knowns old []
| old == knowns = (knowns, unguessable) -- Done
| otherwise = loop unguessable knowns knowns (S.elems knowns)
loop unguessable knowns old (t@(F Cat _) : todo) =
loop unguessable (decat t (S.delete t knowns)) old todo
loop unguessable knowns old ((F Enc [t0, t1]) : todo)
| buildable knowns unguessable (inv t1) = -- Add plaintext
loop unguessable (decat t0 knowns) old todo
| otherwise = loop unguessable knowns old todo
loop unguessable knowns old ((F Hash [_]) : todo) =
loop unguessable knowns old todo -- Hash can't be decomposed
-- New case here: don't delete exponentiated values
loop unguessable knowns old (F Base [F Exp [_, _]] : todo) =
loop unguessable knowns old todo
-- New case here: don't delete exponents that
-- aren't in unguessable
loop unguessable knowns old (t@(G _) : todo)
| S.notMember t unguessable =
loop unguessable knowns old todo
loop unguessable knowns old (t : todo) =
loop (S.delete t unguessable) (S.delete t knowns) old todo
-- Decat
decat (F Cat [t0, t1]) s = decat t1 (decat t0 s)
decat t s = S.insert t s
-- Inverts an asymmetric key
inv :: Term -> Term
inv (F Akey [F Invk [t]]) = F Akey [t]
inv (F Akey [t]) = F Akey [F Invk [t]]
inv (I _) = error "Algebra.inv: Cannot invert a variable of sort mesg"
inv t = t
-- Extracts every encryption that is carried by a term along with its
-- encryption key.
encryptions :: Term -> [(Term, [Term])]
encryptions t =
reverse $ loop t []
where
loop (F Cat [t, t']) acc =
loop t' (loop t acc)
loop t@(F Enc [t', t'']) acc =
loop t' (adjoin (t, [t'']) acc)
loop t@(F Hash [t']) acc =
adjoin (t, [t']) acc
-- loop t@(F Base [F Exp [_, t'']]) acc =
-- adjoin (t, [t'']) acc
loop _ acc = acc
adjoin x xs
| x `elem` xs = xs
| otherwise = x : xs
-- Put a base expression in the form g, g^e, or b or b^e where b is a variable.
expCollapse :: Term -> Term
expCollapse (F Base [F Genr ts]) = F Base [F Genr ts]
-- expCollapse (F Genr _) = F Base [F Genr []]
expCollapse (F Base [F Exp [F Exp [b, G e0], G e1]]) =
case expCollapse (F Base [F Exp [b, G e0]]) of
F Base [F Exp [b', G e0']] -> F Base [F Exp [b', G (mul e0' e1)]]
_ -> error ("Algebra.hs: expCollapse returned non-base element")
expCollapse (F Base [F Exp [b, G e]]) = F Base [F Exp [b, G e]]
expCollapse (F Base [I t]) = F Base [I t]
expCollapse _ = error ("Algebra.hs: expCollapse called on non-base element")
getBase :: Term -> Term
getBase (F Base [(F Genr _)]) = F Base [F Genr []]
getBase t@(F Base _) =
case expCollapse t of
F Base [F Exp [b, _]] -> b
_ -> t -- If not exponentiated, the term is the base.
getBase t = t
relevant :: Set Term -> Term -> Term -> Bool
relevant avoid t1@(F Base _) t2@(F Base _) =
i1 == i2
where
i1 = indicator avoid t1
i2 = indicator avoid t2 -- compare indicators.
relevant _ t1 t2 = t1 == t2
-- Extract the exponent of the term restricted to its map on exponent
-- variables in avoid.
indicator :: Set Term -> Term -> Group
indicator avoid t@(F Base _) =
case expCollapse t of
F Base [F Genr _] -> M.empty
F Base [I _] -> M.empty
F Base [F Exp [_, G m]] -> M.intersection m indicatorBasis
_ -> error ("Algebra.hs: expCollapse returned non-base element")
where
numAvoid = S.map extrExpn $ S.filter isExpn avoid
isExpn (G g) = isBasisVar g
isExpn _ = False
extrExpn (G t) = t
extrExpn _ = error ("Algebra.hs: extrExpn called on a non-exponent")
indicatorBasis = S.fold mul M.empty numAvoid
indicator _ t = error ("Algebra.hs: indicator called on a non-base " ++ show t)
-- Returns the encryptions that carry something relevant to the target.
-- If something relevant to the target is carried outside all encryptions,
-- or is exposed because a decription key is derivable, Nothing is returned.
protectors :: Set Term -> Set Term -> Term -> Term -> Maybe [Term]
protectors avoid sent target source =
-- z ("protectors", avoid, sent, target, source) $
do
ts <- bare source S.empty
return $ S.elems ts
where
bare source acc
| source == target = Nothing
| relevant avoid source target =
case (getBase source == getBase target) of
True -> Nothing
False -> Just (S.insert source acc)
bare (F Cat [t, t']) acc =
maybe Nothing (bare t') (bare t acc)
bare t@(F Enc [t', key]) acc =
if relevantCarriedBy avoid target t' then
if (derivable avoid sent) (inv key) then
bare t' acc
else
Just (S.insert t acc)
else
Just acc
bare t acc =
if relevant avoid target t then
Just (S.insert t acc)
else
Just acc
instance C.Term Term where
derivable = derivable
isNum = isNum
isVar = isVar
isAcquiredVar = isAcquiredVar
forceVar = forceVar
subNums = subNums
indicator = calcIndicator
isAtom = isAtom
isNodeVar = isNodeVar
termsWellFormed = termsWellFormed
occursIn = occursIn
foldVars = foldVars
foldCarriedTerms = foldCarriedTerms
carriedBy = carriedBy
constituent = constituent
decryptionKey = decryptionKey
decompose = decompose
buildable = buildable
encryptions = encryptions
protectors = protectors
loadTerm = loadTerm
-- Places
-- A place names a one subterm within a term. It is a list of
-- integers giving a path through a term to that named subterm. Each
-- integer in the list identifies the subterm in a function
-- application on the path to the named subterm. The integer is the
-- index of the subterm in the application's list of terms.
-- The places and replace code fail to find the variable
-- (F Akey [I x]) in (F Akey [Invk [I x]]).
newtype Place = Place [Int] deriving (Show, Eq)
-- Returns the places a variable occurs within a term.
places :: Term -> Term -> [Place]
places var source =
f [] [] source
where
f paths path source
| var == source = Place (reverse path) : paths
f paths path (F _ u) =
g paths path 0 u
f paths path (G t) =
groupPlaces (varId var) paths path 0 (linearize t)
f paths _ _ = paths
g paths _ _ [] = paths
g paths path i (t : u) =
g (f paths (i: path) t) path (i + 1) u
linearize :: Group -> [Id]
linearize t =
do
(x, (_, n)) <- M.assocs t
replicate (if n >= 0 then n else negate n) x
groupPlaces :: Id -> [Place] -> [Int] -> Int -> [Id] -> [Place]
groupPlaces _ paths _ _ [] = paths
groupPlaces x paths path i (y:ys) =
let paths' = if x == y then
Place (reverse (i : path)) : paths
else paths in
groupPlaces x paths' path (i + 1) ys
-- Returns the places a term is carried by another term.
carriedPlaces :: Term -> Term -> [Place]
carriedPlaces target source =
f [] [] source
where
f paths path source
| target == source = Place (reverse path) : paths
f paths path (F Cat [t, t']) =
f (f paths (0 : path) t) (1 : path) t'
f paths path (F Enc [t, _]) =
f paths (0 : path) t
-- f paths path (F Base [F Exp [_, t]]) =
-- f paths (1 : 0 : path) t
f paths _ _ = paths
-- Returns the places a term is carried by another term.
carriedRelPlaces :: Term -> Term -> Set Term -> [Place]
carriedRelPlaces target source avoid =
f [] [] source
where
f paths path source
| relevant avoid source target = Place (reverse path) : paths
f paths path (F Cat [t, t']) =
f (f paths (0 : path) t) (1 : path) t'
f paths path (F Enc [t, _]) =
f paths (0 : path) t
-- f paths path (F Base [F Exp [_, t]]) =
-- f paths (1 : 0 : path) t
f paths _ _ = paths
-- Replace a variable within a term at a given place.
replace :: Term -> Place -> Term -> Term
replace var (Place ints) source =
loop ints source
where
loop [] _ = var
loop (i : path) (F s u) =
F s (C.replaceNth (loop path (u !! i)) i u)
loop _ _ = C.assertError "Algebra.replace: Bad path to term"
factors :: Group -> [(Id, (Bool, Int))]
factors t =
do
(x, (be, n)) <- M.assocs t
case n >= 0 of
True -> replicate n (x, (be, 1))
False -> replicate (negate n) (x, (be, -1))
-- Return the ancestors of the term at the given place.
ancestors :: Term -> Place -> [Term]
ancestors source (Place ints) =
loop [] ints source
where
loop ts [] _ = ts
loop ts (i: path) t@(F _ u) =
loop (t : ts) path (u !! i)
loop ts [_] t@(G _) = t : ts
loop _ _ _ = C.assertError "Algebra.ancestors: Bad path to term"
prefix :: Place -> Place -> Bool
prefix (Place l) (Place l') = L.isPrefixOf l l'
strip :: Place -> Place -> Maybe Place
strip (Place l) (Place l') =
loop l l'
where
loop [] l' = Just $ Place l'
loop (i : l) (i' : l') | i == i' = loop l l'
loop _ _ = Nothing
instance C.Place Term Place where
places = places
carriedPlaces = carriedPlaces
carriedRelPlaces = carriedRelPlaces
replace = replace
ancestors = ancestors
placeIsPrefixOf = prefix
placeStripPrefix = strip
-- Genericize: transform h^e -> (h^e)^e' where e' is a cloned
-- variable, cloned off the first variable in e.
genericize :: Gen -> Term -> (Gen, Term)
genericize gen (F Base [F Exp [base, G grp]]) =
(gen', F Base [F Exp [base, G (mul grp exp')]])
where
(gen', gexp') = clone gen (G exp)
exp = if (null (M.toList grp)) then
C.assertError "Algebra.genericize: Something odd happened"
else
M.fromList [let (x, (_,n)) = head (M.toList grp) in
(x, (False,n))]
exp' = case gexp' of
G x -> x
_ -> C.assertError "Algebra.genericize: Something odd happened"
genericize g t = (g, t)
{-
genericVersion :: Gen -> Term -> (Gen, Term)
genericVersion gen (F Base [F Exp [F Genr _, G grp]]) =
(gen', (F Base [F Exp [I id', G grp]]))
where
(gen', id') = freshId gen "g"
genericVersion gen (F Base [F Exp [I id, G grp]]) =
(gen', (F Base [F Exp [t', G grp]]))
where
(gen', t') = clone gen (I id)
genericVersion gen (F Base [F Exp [F Exp [base, G grp], G grp']]) =
genericVersion gen (F Base [F Exp [base, G (mul grp grp')]])
genericVersion gen t = (gen, t)
-}
-- Rename the identifiers in a term. Gen keeps the state of the
-- renamer. (Question: should alist be replaced by a Map?)
clone :: Gen -> Term -> (Gen, Term)
clone gen t =
(gen', t')
where
(_, gen', t') = cloneTerm ([], gen) t
cloneTerm (alist, gen) t =
case t of -- The association list maps
I x -> -- identifiers to identifier.
case lookup x alist of
Just y -> (alist, gen, I y)
Nothing ->
let (gen', y) = cloneId gen x in
((x, y) : alist, gen', I y)
C c -> (alist, gen, C c)
F sym u ->
let (alist', gen', u') =
foldl cloneTermList (alist, gen, []) u in
(alist', gen', F sym $ reverse u')
G t ->
let (alist', gen', ts) =
M.foldlWithKey cloneGroupList (alist, gen, []) t in
(alist', gen', G $ group ts)
D x -> -- identifiers to identifier.
case lookup x alist of
Just y -> (alist, gen, D y)
Nothing ->
let (gen', y) = cloneId gen x in
((x, y) : alist, gen', D y)
P p -> (alist, gen, P p)
cloneTermList (alist, gen, u) t =
let (alist', gen', t') = cloneTerm (alist, gen) t in
(alist', gen', t' : u)
cloneGroupList (alist, gen, ts) x (be, n) =
case lookup x alist of
Just y -> (alist, gen, (y, (be, n)) : ts)
Nothing ->
let (gen', y) = cloneId gen x in
((x, y) : alist, gen', (y, (be, n)) : ts)
instance C.Gen Term Gen where
origin = origin
genericize = genericize
-- genericVersion = genericVersion
clone = clone
loadVars = loadVars
-- Functions used in both unification and matching
type IdMap = Map Id Term
emptyIdMap :: IdMap
emptyIdMap = M.empty
-- Apply a substitution to a term
idSubst :: IdMap -> Term -> Term
idSubst _ (F Exp []) = C.assertError "DiffieHellman.Algebra: Bad exponentiation"
idSubst subst (I x) =
M.findWithDefault (I x) x subst
idSubst _ t@(C _) = t
idSubst subst (F Invk [t]) =
case idSubst subst t of
F Invk [t] -> t -- (invk (invk x)) = x
t -> F Invk [t]
idSubst subst (F Exp [t0, G t1]) =
case idSubst subst t0 of -- (exp (exp g x) y) = (exp g (mul x y))
F Exp [t0', G t1'] ->
case mul t1' $ groupSubst subst t1 of
t2 | M.null t2 -> t0'
| otherwise -> F Exp [t0', G t2]
t -> expSubst subst t t1
idSubst subst (F s u) =
F s (map (idSubst subst) u)
idSubst subst (G t) =
G $ groupSubst subst t
idSubst subst (D x) =
M.findWithDefault (D x) x subst
idSubst _ t@(P _) = t
expSubst :: IdMap -> Term -> Group -> Term
expSubst subst t0 t1 =
case groupSubst subst t1 of
t1' | M.null t1' -> t0 -- (exp g (one)) = g
| otherwise -> F Exp [t0, G t1']
groupSubst :: IdMap -> Group -> Group
groupSubst subst t =
M.foldrWithKey f M.empty t
where
f x (be, c) t =
mul (expg (groupLookup subst be x) c) t
groupLookup :: IdMap -> Bool -> Id -> Group
groupLookup subst be x =
case M.findWithDefault (groupVar be x) x subst of
G t -> t
w -> error ("Algebra.groupLookup: Bad substitution: " ++
show x ++ " -> " ++ show w)
showMap :: (Show a, Show b) => Map a b -> ShowS
showMap m =
showAssocs (M.assocs m)
where
showAssocs [] = id
showAssocs ((x,y):m) =
showString "\n " . shows x . showString " -> " .
shows y . showAssocs m
-- Unification and substitution
-- The rewrite rules used are:
--
-- (vars (h base) (x y expn))
--
-- 1. ((exp h x) y) ==> (exp h (mul x y))
-- 2. (exp h (one)) ==> h
-- 3. unify((exp(h, x)), (exp(h, y)), s) ==>
-- unify(x, y, s)
-- 4 unify((exp(h, x)), (exp((gen), y)), s) ==>
-- unify(h, (exp gen (mul y (rec x))), s)
-- 5. unify((exp((gen), x)), (exp(h, y)), s) ==>
-- unify((exp(h, x)), (exp((gen), y)), s)
newtype Subst = Subst IdMap deriving (Eq, Ord)
instance Show Subst where
showsPrec _ (Subst s) = showString "Subst (" . showMap s . showChar ')'
emptySubst :: Subst
emptySubst = Subst emptyIdMap
-- Apply a substitution created by unification
substitute :: Subst -> Term -> Term
substitute (Subst s) t =
idSubst s t
-- Composition of substitutions
-- substitute (compose s0 s1) t = substitute s0 (substitute s1 t)
-- 1. apply s0 to range of s1 to obtain s2;
-- 2. remove bindings is s0 where domains of s0 and s1 overlap to form s3;
-- 3. remove trivial bindings from s2 to form s4; and
-- 4. take the union of s4 and s3.
compose :: Subst -> Subst -> Subst
compose (Subst s0) (Subst s1) =
let s2 = M.map (substitute (Subst s0)) s1 -- Step 1
s4 = M.filterWithKey nonTrivialBinding s2 in -- Step 3
Subst (M.union s4 s0) -- Steps 2 and 4, union is left-biased
nonTrivialBinding :: Id -> Term -> Bool
nonTrivialBinding x (I y) = x /= y
nonTrivialBinding x t@(G _) = not (t == groupVar True x || t == groupVar False x)
nonTrivialBinding _ _ = True
-- During unification, variables determined to be equal are collected
-- into an equivalence class. Multiple lookups of each variable in
-- the internal representation of a substitution finds the canonical
-- representive of the class. The chase function finds the current
-- canonical representitive.
-- Get the canonical representative of equivalent identifiers making use
-- of this algebra's axiom.
chase :: Subst -> Term -> Term
chase (Subst s) (I x) =
case M.lookup x s of
Nothing -> I x
Just t -> chase (Subst s) t
chase (Subst s) (D x) =
case M.lookup x s of
Nothing -> D x
Just t -> chase (Subst s) t
chase s (F Invk [t]) = chaseInvk s t
chase s (F Exp [t0, G t1]) = chaseExp s t0 t1
chase _ t = t
chaseInvk :: Subst -> Term -> Term
chaseInvk (Subst s) (I x) =
case M.lookup x s of
Nothing -> F Invk [I x]
Just t -> chaseInvk (Subst s) t
chaseInvk s (F Invk [t]) = chase s t
chaseInvk _ t = F Invk [t]
chaseExp :: Subst -> Term -> Group -> Term
chaseExp s t0 t1
| M.null t1 = chase s t0
chaseExp s@(Subst ss) (I x) t1 =
case chase s (I x) of
F Exp [t0', G t1'] -> -- chaseExp s t0' (mul t1 t1')
if M.null t1t1'
then t0'
else F Exp [t0', G t1t1']
where t1t1' = mul t1' (groupSubst ss t1)
t0 -> F Exp [t0, chaseGroup s t1]
chaseExp s (F Exp [t0', G t1']) t1 =
chaseExp s t0' (mul t1 t1')
chaseExp s t0 t1 = F Exp [t0, chaseGroup s t1]
chaseGroup :: Subst -> Group -> Term
chaseGroup (Subst s) x = G $ groupSubst s x
-- Does x occur in t?
occurs :: Id -> Term -> Bool
occurs x (I y) = x == y
occurs _ (C _) = False
occurs x (F _ u) = any (occurs x) u
occurs x (G t) = elem x (M.keys t)
occurs x (D y) = x == y
occurs _ (P _) = False
type GenSubst = (Gen, Subst)
unifyChase :: Term -> Term -> GenSubst -> [GenSubst]
unifyChase t t' (g, s) = unifyTerms (chase s t) (chase s t') (g, s)
unifyTerms :: Term -> Term -> GenSubst -> [GenSubst]
unifyTerms (I x) (I y) (g, Subst s)
| x == y = [(g, Subst s)]
| otherwise = [(g, Subst $ M.insert x (I y) s)]
unifyTerms (I x) t (g, Subst s)
| occurs x t = []
| otherwise = [(g, Subst $ M.insert x t s)]
unifyTerms t (I x) s = unifyTerms (I x) t s
unifyTerms (C c) (C c') s
| c == c' = [s]
| otherwise = []
unifyTerms (F Invk [I x]) (F Pubk [I y]) s =
unifyTerms (I x) (F Invk [F Pubk [I y]]) s
unifyTerms (F Invk [I x]) (F Pubk [C c, I y]) s =
unifyTerms (I x) (F Invk [F Pubk [C c, I y]]) s
unifyTerms (F Pubk [I x]) (F Invk [I y]) s =
unifyTerms (I y) (F Invk [F Pubk [I x]]) s
unifyTerms (F Pubk [C c, I x]) (F Invk [I y]) s =
unifyTerms (I y) (F Invk [F Pubk [C c, I x]]) s
unifyTerms (F Bltk u) (F Bltk u') s =
L.nub $ unifyTermLists u u' s ++ unifyTermLists u (reverse u') s
unifyTerms (F Base [t0]) (F Base [t1]) s =
unifyBase t0 t1 s
unifyTerms (F sym u) (F sym' u') s
| sym == sym' = unifyTermLists u u' s
| otherwise = []
unifyTerms (G t) (G t') s =
unifyGroup t t' s
unifyTerms (D x) (D y) (g, Subst s)
| x == y = [(g, Subst s)]
| otherwise = [(g, Subst $ M.insert x (D y) s)]
unifyTerms (D x) (P p) (g, Subst s) =
[(g, Subst $ M.insert x (P p) s)]
unifyTerms t (D x) s = unifyTerms (D x) t s
unifyTerms (P p) (P p') s
| p == p' = [s]
| otherwise = []
unifyTerms _ _ _ = []
-- unifyBase: the two terms were both encapsulated in F Base [].
-- feed the appropriate inputs to unifyExp.
-- Take h to mean h^1 for either base variable h or h = (gen).
-- Fall back to unify algorithm when left side is F Genr [].
unifyBase :: Term -> Term -> GenSubst -> [GenSubst]
unifyBase (F Exp [t0, G t1]) (F Exp [t0', G t1']) gs
= unifyExp t0 t1 t0' t1' gs
unifyBase (F Exp [t0, G t1]) (I x) gs
= unifyExp t0 t1 (I x) (M.empty) gs
unifyBase (F Exp [t0, G t1]) (F Genr []) gs
= unifyExp t0 t1 (F Genr []) (M.empty) gs
unifyBase (I x) (F Exp [t0', G t1']) gs
= unifyExp (I x) (M.empty) t0' t1' gs
unifyBase (I x) (I y) gs
= unifyExp (I x) (M.empty) (I y) (M.empty) gs
unifyBase (I x) (F Genr []) gs
= unifyExp (I x) (M.empty) (F Genr []) (M.empty) gs
unifyBase t0 t1 gs
= unifyTerms t0 t1 gs
-- unifyExp: not guaranteed that inputs are not F Exp expressions.
-- guaranteed that t0 and t0' are (I x), (F Genr _), or (F Exp _).
-- Both t0 and t0' should not be F Exp, though, as this would indicate a non-canonical form.
unifyExp :: Term -> Group -> Term -> Group -> GenSubst -> [GenSubst]
unifyExp (F Exp t0) t1 _ _ _ =
error ("Algebra.unifyExp: Got input not in canonical form " ++ show (F Exp t0) ++ show t1)
unifyExp _ _ (F Exp t0) t1 _ =
error ("Algebra.unifyExp: Got input not in canonical form " ++ show (F Exp t0) ++ show t1)
-- Force into canonical form.
--unifyExp (F Exp [t0, G e]) t1 t0' t1' gs =
-- unifyExp t0 (mul e t1) t0' t1' gs
--unifyExp t0 t1 (F Exp [t0', G e]) t1' gs =
-- unifyExp t0 t1 t0' (mul e t1') gs
unifyExp t0 t1 t0' t1' s
| t0 == t0' = unifyGroup t1 t1' s
unifyExp (I x1) t0 (I x2) t1' (g, Subst s) =
unifyGroup (mul t0 z) t1' (g', Subst $ M.insert x1 (F Exp [(I x2), G z]) s)
where
(g', zid) = freshId g "z"
z = groupVarGroup zid
unifyExp (I x) t1 (F Genr []) t1' (g, Subst s)
| t1 == t1' =
[(g, Subst $ M.insert x (F Genr []) s)]
| otherwise =
[(g, Subst (M.insert x (F Exp [F Genr [], G $ mul t1' (invert t1)]) s))]
unifyExp (F Genr []) t1 (I x) t1' s =
unifyExp (I x) t1' (F Genr []) t1 s
unifyExp _ _ _ _ _ = []
unifyTermLists :: [Term] -> [Term] -> GenSubst -> [GenSubst]
unifyTermLists [] [] s = [s]
unifyTermLists (t : u) (t' : u') s =
do
s' <- unifyChase t t' s
unifyTermLists u u' s'
unifyTermLists _ _ _ = []
unifyGroup :: Group -> Group -> GenSubst -> [GenSubst]
unifyGroup t0 t1 (g, Subst s) =
do
let t = groupSubst s (mul t0 (invert t1))
(_, g', s') <- matchGroup t M.empty S.empty g s
return (g', Subst s')
-- The exported unifier converts the internal representation of a
-- substitution into the external form using chaseMap.
unify :: Term -> Term -> GenSubst -> [GenSubst]
unify t t' s =
do
(g, s) <- unifyChase t t' s
return (g, chaseMap s)
-- Apply the chasing version of substitution to the range of s.
chaseMap :: Subst -> Subst
chaseMap (Subst s) =
Subst $ M.map (substChase (Subst s)) s
-- A chasing version of substitution.
substChase :: Subst -> Term -> Term
substChase subst t =
case chase subst t of
t@(I _) -> t
t@(C _) -> t
F Invk [t] ->
case substChase subst t of
F Invk [t] -> t -- Apply axiom
t -> F Invk [t]
F Exp [t0, G t1] ->
case substChase subst t0 of
F Exp [t0', G t1'] ->
case mul t1' $ groupChase subst t1 of
t2 | M.null t2 -> t0'
| otherwise -> F Exp [t0', G t2]
t -> expChase subst t t1
F s u ->
F s (map (substChase subst) u)
G t -> G $ groupChase subst t
t@(D _) -> t
t@(P _) -> t
expChase :: Subst -> Term -> Group -> Term
expChase subst t0 t1 =
case groupChase subst t1 of
t1' | M.null t1' -> t0
| otherwise -> F Exp [t0, G t1']
groupChase :: Subst -> Group -> Group
groupChase (Subst subst) t = groupSubst subst t
destroyer :: Term -> Maybe Subst
destroyer t@(G m) | isVar t =
Just $ Subst (M.fromList [(head $ M.keys m, G M.empty)])
destroyer _ = Nothing
instance C.Subst Term Gen Subst where
emptySubst = emptySubst
destroyer = destroyer
substitute = substitute
unify = unify
compose = compose
-- Matching and instantiation
newtype Env = Env (Set Id, IdMap) deriving (Eq, Ord)
instance Show Env where
showsPrec _ (Env (v, r)) =
showString "Env (\n " . shows v .
showChar ',' . showMap r . showChar ')'
-- An environment may contain an explicit identity mapping, whereas a
-- substitution is erroneous if it has one. The set of variables
-- associated with a map is the variables in the range that were
-- generated by matching and should be treated as variables when using
-- unification to perform matching. The other variables in the range
-- are treated as constants.
-- An environment contains an IdMap and the set of variables
-- generated while matching.
emptyEnv :: Env
emptyEnv = Env (S.empty, emptyIdMap)
-- Apply a substitution created my matching
instantiate :: Env -> Term -> Term
instantiate (Env (_, r)) t = idSubst r t
-- Matching
type GenEnv = (Gen, Env)
-- The matcher has the property that when pattern P and term T match
-- then instantiate (match P T emptyEnv) P = T.
-- Important discipline to maintain during match:
-- Terms are either "source" or "destination / flex" terms, and these two
-- categories should be kept strictly separate.
-- the first parameter is a source term.
-- the second parameter is a destination/flex term.
-- v is a set of destination/flex IDs.
-- g is a generator for the destination/flex algebra
-- variables in the domain of r are source variables
-- terms in the range of r are destination/flex terms.
match :: Term -> Term -> GenEnv -> [GenEnv]
match (I x) t (g, Env (v, r)) =
case M.lookup x r of
Nothing -> [(g, Env (v, M.insert x t r))]
Just t' -> if t == t' then [(g, Env (v, r))] else []
match (C c) (C c') ge = if c == c' then [ge] else []
match (F Base [t0]) (F Base [t1]) ge =
matchBase t0 t1 ge
match (F Bltk u) (F Bltk u') ge =
L.nub $ matchLists u u' ge ++ matchLists u (reverse u') ge
match (F s u) (F s' u') ge
| s == s' = matchLists u u' ge
match (F Invk [t]) t' ge =
match t (F Invk [t']) ge
match (G t) (G t') (g, Env (v, r)) =
do
(v', g', r') <- matchGroup t t' v g r
return (g', Env(v', r'))
match (D x) t (g, Env (v, r)) =
case M.lookup x r of
Nothing -> [(g, Env (v, M.insert x t r))]
Just t' -> if t == t' then [(g, Env (v, r))] else []
match (P p) (P p') r = if p == p' then [r] else []
match _ _ _ = []
-- On input t, outputs (b, e) such that if t is of sort base then
-- t = b^e and b is a variable or (gen).
-- If t is not of sort base, outputs (t, 1).
calcBase :: Term -> (Term, Group)
calcBase (I x) = ((I x), M.empty)
calcBase (F Genr _) = (F Genr [], M.empty)
calcBase (F Exp [(I x), G e]) = ((I x), e)
calcBase (F Exp [F Genr _, G e]) = (F Genr [], e)
calcBase (F Exp [F Exp [b, G e1], G e2]) = calcBase (F Exp [b, G $ mul e1 e2])
-- Well-formed versions. Is this necessary?
calcBase (F Base [I x]) = (F Base [I x], M.empty)
calcBase (F Base [F Genr _]) = (F Base [F Genr []], M.empty)
calcBase (F Base [F Exp [(I x), G e]]) = (F Base [I x], e)
calcBase (F Base [F Exp [F Genr _, G e]]) = (F Base [F Genr []], e)
calcBase (F Base [F Exp [F Exp [b, G e1], G e2]]) = calcBase (F Base [F Exp [b, G $ mul e1 e2]])
calcBase t = (t, M.empty)
-- matchBase: the two terms were both encapsulated in F Base [].
-- feed the appropriate inputs to matchExp.
-- Take h to mean h^1 for either base variable h or h = (gen).
-- Fall back to match algorithm when left side is F Genr [].
matchBase :: Term -> Term -> GenEnv -> [GenEnv]
matchBase (F Exp [t0, G t1]) (F Exp [t0', G t1']) ge
= matchExp t0 t1 t0' t1' ge
matchBase (F Exp [t0, G t1]) (I x) ge
= matchExp t0 t1 (I x) (M.empty) ge
matchBase (F Exp [t0, G t1]) (F Genr []) ge
= matchExp t0 t1 (F Genr []) (M.empty) ge
matchBase (I x) (F Exp [t0', G t1']) ge
= matchExp (I x) (M.empty) t0' t1' ge
matchBase (I x) (I y) ge
= matchExp (I x) (M.empty) (I y) (M.empty) ge
matchBase (I x) (F Genr []) ge
= matchExp (I x) (M.empty) (F Genr []) (M.empty) ge
matchBase t0 t1 ge
= match t0 t1 ge
{-
case M.lookup x r of
Nothing -> match (I x) (F Exp [b,e]) (g, Env (v,r))
Just (F Exp [b',e']) -> if (bb == bb') then
match (G M.empty) (G (mul ee (invert ee'))) (g, Env (v, r)) else []
where
(bb', ee') = calcBase (F Exp [b',e'])
Just (I y) -> if ((I y) == bb) then
match (G M.empty) (G ee) (g, Env (v, r)) else []
Just _ -> []
where
(bb, ee) = calcBase t1
match (F Base [I x]) (F Base [I y]) (g, Env (v, r)) =
case M.lookup x r of
Nothing -> match (I x) (I y) (g, Env (v,r))
Just (F Exp [b',e']) -> if (bb' == (I y)) then
match (G M.empty) (G ee') (g, Env (v,r)) else []
where
(bb', ee') = calcBase (F Exp [b',e'])
Just _ -> match (I x) (I y) (g, Env (v, r))
-}
-- matchExp: not guaranteed that inputs are not F Exp expressions.
-- guaranteed that t0 is either an I x or an F Exp [] term.
-- guaranteed that t0' is I x, F Genr, or F Exp.
-- in match t0 t1 t0' t1' ge: t0 and t1 are source material, t0', t1' are destination/flex material.
-- Both t0 and t0' should not be F Exp, though, as this would indicate a non-canonical form.
matchExp :: Term -> Group -> Term -> Group -> GenEnv -> [GenEnv]
matchExp (F Exp [t0, G e]) t1 _ _ _ =
error ("Algebra.matchExp: Input not in canonical form" ++ show (F Exp [F Exp [t0, G e], G t1]))
matchExp _ _ (F Exp [t0, G e]) t1 _ =
error ("Algebra.matchExp: Input not in canonical form" ++ show (F Exp [F Exp [t0, G e], G t1]))
-- Force both inputs into canonical form
--matchExp (F Exp [t0, G e]) t1 t0' t1' ge =
-- matchExp t0 (mul e t1) t0' t1' ge
--matchExp t0 t1 (F Exp [t0', G e]) t1' ge =
-- matchExp t0 t1 t0' (mul e t1') ge
matchExp (I x) t1 t0' t1' ge@(g, Env (v, r)) =
case M.lookup x r of
-- if x is already mapped, it needs to be mapped to a power of the base of t0'
Just t -- t is destination/flex material
| fst (calcBase t0') == fst (calcBase t) ->
match (G t1) (G (mul t1' (mul (snd $ calcBase t0') (invert (snd $ calcBase t))))) ge
| otherwise -> []
_ -> matchLists [I x, G t1] [F Exp [t0', G w], G (mul t1' (invert w))]
(g', Env (S.insert wid v, r))
where
(g', wid) = freshId g "w"
w = groupVarGroup wid
matchExp (F Genr []) t1 t0' t1' ge =
matchLists [F Genr [], G t1] [t0', G t1'] ge
matchExp t e t' e' _ = error ("Algebra.matchExp: Bad match term" ++ show t ++ show e ++ show t' ++ show e')
-- in matchLists u u' ge: u is a list of source terms and u' is a list of destination/flex terms.
matchLists :: [Term] -> [Term] -> GenEnv -> [GenEnv]
matchLists [] [] ge = [ge]
matchLists (t : u) (t' : u') ge =
do
ge' <- match t t' ge
matchLists u u' ge'
matchLists _ _ _ = []
-- Matching in a group
-- t0 is the pattern
-- t1 is the target term
-- v is the set of previously freshly generated variables
-- g is the generator
-- Returns complete set of unifiers. Each unifier include the set of
-- variables fresh generated and a generator.
matchGroup :: Group -> Group -> Set Id -> Gen ->
IdMap -> [(Set Id, Gen, IdMap)]
matchGroup t0 t1 v g r =
let (t0', t1') = merge t0 t1 r -- Apply subst to LHS
(v', g', r') = genVars v g t0' r -- Gen vars for non-fresh vars
d = mkInitMatchDecis t1' in -- Ensure expns on RHS stay distinct
case partition (groupSubst r' t0') t1' v' of
([], []) -> return (v', g', r')
([], t) -> constSolve t v' g' r' d -- No variables of sort expr here
(t0, t1) -> solve t0 t1 v' g' r' d
-- Apply subst to LHS and add results to RHS
merge :: Group -> Group -> IdMap -> (Group, Group)
merge t t' r =
(group t0, t0')
where
(t0, t0') = loop (M.assocs t) ([], t')
loop [] acc = acc
loop (p@(x, (_, c)) : t0) (t1, t1') =
case M.lookup x r of
Nothing -> loop t0 (p : t1, t1')
Just (G t) ->
loop t0 (t1, mul (expg t (negate c)) t1')
Just t ->
error $ "Algebra.merge: expecting an expn but got " ++ show t
-- Generate vars for each non-fleshly generated vars
genVars :: Set Id -> Gen -> Group -> IdMap -> (Set Id, Gen, IdMap)
genVars v g t r =
M.foldlWithKey genVar (v, g, r) t
where
genVar (v, g, r) x (be, _) =
(S.insert x' v, g', M.insert x (groupVar be x') r)
where
(g', x') = cloneId g x
-- A set of decisions records expn variables that have been identified
-- and those that are distinct.
data Decision t = Decision
{ same :: [(t, t)],
dist :: [(t, t)] }
deriving Show
-- Create an initial set of decisions
mkDecis :: Decision Id
mkDecis =
Decision {
same = [],
dist = [] }
-- Ensure bases elements in t are never identified
mkInitMatchDecis :: Group -> Decision Id
mkInitMatchDecis t =
mkDecis { dist = [(x, y) | x <- v, y <- v, x /= y] }
where
v = [x | (x, (be, _)) <- M.assocs t, be]
-- Move fresh variables on the RHS of the equation to the LHS
-- Move variables of sort expn on the LHS to the RHS
partition :: Group -> Group -> Set Id -> ([Maplet], [Maplet])
partition t0 t1 v =
(M.assocs lhs, M.assocs rhs)
where
(v1, c1) = M.partitionWithKey g t1 -- Fresh variables go in v1
g x _ = S.member x v
(v0, c0) = M.partition f t0 -- Basis elements go in c0
f (be, _) = not be
lhs = mul v0 (invert v1)
rhs = mul c1 (invert c0)
-- Solve equation when there are no variables of sort expr on LHS.
-- Treat all variables as constants.
constSolve :: [Maplet] -> Set Id -> Gen -> IdMap ->
Decision Id -> [(Set Id, Gen, IdMap)]
constSolve t v g r d
| any (\(_, (be, _)) -> not be) t = [] -- Fail expr var is on RHS
| otherwise = constSolve1 t v g r d -- All vars are expn
constSolve1 :: [Maplet] -> Set Id -> Gen ->
IdMap -> Decision Id -> [(Set Id, Gen, IdMap)]
constSolve1 [] v g r _ = return (v, g, r)
constSolve1 t v g r d =
case orientDecis v $ nextDecis d t of
[] -> [] -- All decisions already made
((x, y):_) -> -- Pick first undecided pair
distinct ++ identified
where
distinct = constSolve1 t v g r neq
neq = d {dist = (x, y):(y, x):dist d} -- Add new constraints
-- eliminate x
identified = constSolve1 t' v' g r' d'
t' = identify x y t -- Equate x y in t
v' = S.delete x v -- Eliminate x in v
r' = eliminate x y' r -- And in r
y' = groupVar True y
d' = d {same = (x, y):same d} -- And note decision
-- Find a pair of variables for which no decision has been made.
nextDecis :: Decision Id -> [Maplet] -> [(Id, Id)]
nextDecis d t =
[(x, y) | x <- vars, y <- vars, x < y,
not $ decided d x y]
where
vars = foldr f [] t
f (x, (True, _)) v = x:v
f (_, (False, _)) v = v
decided d x y = -- Is x and y decided?
u == v ||
any f (dist d)
where
u = chase x -- Find canonical representitive for x and y
v = chase y
f (w, z) = chase w == u && chase z == v
chase = listChase (same d)
-- Find canonical representive of the set of identified variables.
listChase :: Eq t => [(t, t)] -> t -> t
listChase d x =
case lookup x d of
Nothing -> x
Just y -> listChase d y
-- Ensure first var in pair is in v.
orientDecis :: Set Id -> [(Id, Id)] -> [(Id, Id)]
orientDecis v undecided =
map f undecided
where
f (x, y)
| S.notMember x v = (y, x)
| otherwise = (x, y)
-- Modify t by replacing x by y.
identify :: Id -> Id -> [Maplet] -> [Maplet]
identify x y t =
case lookup x t of
Nothing -> error ("Algebra.identify: bad lookup of " ++ show x
++ " in " ++ show t)
Just (_, c) ->
filter f (map g t)
where
f (z, (_, c)) = z /= x && c /= 0
g m@(z, (be, d))
| z == y = (z, (be, c + d))
| otherwise = m
-- Solve when variables of sort expr are on LHS. This involves
-- solving using the group axioms. The algorithm for matching in the
-- group without added constant symbols is the same as the one for
-- unification with constant symbols.
--
-- For this description, additive notation is used for the group. To
-- show sums, we write
--
-- sum[i] c[i]*x[i] for c[0]*x[0] + c[1]*x[1] + ... + c[n-1]*x[n-1].
--
-- The unification problem is to solve
--
-- sum[i] c[i]*x[i] = sum[j] d[j]*y[j]
--
-- where x[i] is a variable and y[j] is a constant symbol.
--
-- The algorithm used to find solutions is described in Vol. 2 of The
-- Art of Computer Programming / Seminumerical Alorithms, 2nd Ed.,
-- 1981, by Donald E. Knuth, pg. 327.
--
-- The algorithm's initial values are the linear equation (c,d) and an
-- empty substitution s.
--
-- 1. Let c[i] be the smallest non-zero coefficient in absolute value.
--
-- 2. If c[i] < 0, multiply c and d by -1 and goto step 1.
--
-- 3. If c[i] = 1, a general solution of the following form has been
-- found:
--
-- x[i] = sum[j] -c'[j]*x[j] + d[k] for all k
--
-- where c' is c with c'[i] = 0. Use the equation to eliminate x[i]
-- from the range of the current substitution s. If variable x[i] is
-- in the original equation, add the mapping to substitution s.
--
-- 4. If c[i] divides every coefficient in c,
--
-- * if c[i] divides every constant in d, divide c and d by c[i]
-- and goto step 3,
--
-- * otherwise fail because there is no solution. In this case
-- expn vars must be identified.
--
-- 5. Otherwise, eliminate x[i] as above in favor of freshly created
-- variable x[n], where n is the length of c.
--
-- x[n] = sum[j] (c[j] div c[i] * x[j])
--
-- Goto step 1 and solve the equation:
--
-- c[i]*x[n] + sum[j] (c[j] mod c[i])*x[j] = d[k] for all k
solve :: [Maplet] -> [Maplet] -> Set Id -> Gen ->
IdMap -> Decision Id -> [(Set Id, Gen, IdMap)]
solve t0 t1 v g r d =
let (x, ci, i) = smallest t0 in -- ci is the smallest coefficient,
case compare ci 0 of -- x is its variable, i its position
GT -> agSolve x ci i t0 t1 v g r d
LT -> agSolve x (-ci) i (mInverse t0) (mInverse t1) v g r d -- Step 2
EQ -> C.assertError "Algebra.solve: zero coefficient found"
-- Find the factor with smallest coefficient in absolute value.
-- Returns the variable, the coefficient, and the position within the
-- list.
smallest :: [Maplet] -> (Id, Int, Int)
smallest [] = C.assertError "Algebra.smallest given an empty list"
smallest t =
loop (Id (0, "x")) 0 0 0 0 t
where
loop v ci i _ _ [] = (v, ci, i)
loop v ci i a j ((x, (_, c)):t) =
if a < abs c then
loop x c j (abs c) (j + 1) t
else
loop v ci i a (j + 1) t
-- The group axioms are abbreviated by AG.
agSolve :: Id -> Int -> Int -> [Maplet] -> [Maplet] -> Set Id -> Gen ->
IdMap -> Decision Id -> [(Set Id, Gen, IdMap)]
agSolve x 1 i t0 t1 v g r _ = -- Solve for x and return answer
return (S.delete x v, g, eliminate x t r) -- Step 3
where
t = G $ group (t1 ++ (mInverse (omit i t0)))
agSolve x ci i t0 t1 v g r d
| divisible ci t0 = -- Step 4
if divisible ci t1 then -- Solution found
agSolve x 1 i (divide ci t0) (divide ci t1) v g r d
else -- No possible solution without identifying variables
identSolve x ci i t0 t1 v g r d
| otherwise = -- Step 5, eliminate x in favor of x'
solve t0' t1 (S.insert x' $ S.delete x v) g' r' d
where
(g', x') = cloneId g x
t = G $ group ((x', (False, 1)) :
mInverse (divide ci (omit i t0)))
r' = eliminate x t r
t0' = (x', (False, ci)) : modulo ci (omit i t0)
eliminate :: Id -> Term -> IdMap -> IdMap
eliminate x t r =
M.map (idSubst (M.singleton x t)) r
omit :: Int -> [a] -> [a]
omit 0 (_:l) = l
omit n _ | n < 0 = C.assertError "Algebra.omit: negative number given to omit"
omit n (_:l) = omit (n - 1) l
omit _ [] = C.assertError "Algebra.omit: number given to omit too large"
divisible :: Int -> [Maplet] -> Bool
divisible ci t =
all (\(_, (_, c)) -> mod c ci == 0) t
divide :: Int -> [Maplet] -> [Maplet]
divide ci t = map (mMapCoef $ flip div ci) t
modulo :: Int -> [Maplet] -> [Maplet]
modulo ci t =
[(x, (be, c')) |
(x, (be, c)) <- t,
let c' = mod c ci,
c' /= 0]
-- Explore two choices as to whether to identify a pair of variables.
identSolve :: Id -> Int -> Int -> [Maplet] -> [Maplet] -> Set Id -> Gen ->
IdMap -> Decision Id -> [(Set Id, Gen, IdMap)]
identSolve z ci i t0 t1 v g r d =
case orientDecis v $ nextDecis d t1 of
[] -> []
((x, y):_) ->
distinct ++ identified
where
distinct = identSolve z ci i t0 t1 v g r neq
neq = d {dist = (x, y):(y, x):dist d}
-- eliminate x
identified = agSolve z ci i t0 t1' v' g r' d'
t1' = identify x y t1 -- Equate x y in t1
v' = S.delete x v -- Eliminate x in v
r' = eliminate x y' r -- And in r
y' = groupVar True y
d' = d {same = (x, y):same d}
-- Does every varible in ts not occur in the domain of e?
-- Trivial bindings in e are ignored.
identityEnvFor :: GenEnv -> [Term] -> Maybe GenEnv
identityEnvFor ge ts =
let env@(_, Env (_, r)) = nonTrivialEnv ge in
if all (allId $ flip S.notMember $ M.keysSet r) ts then
Just env
else
Nothing
allId :: (Id -> Bool) -> Term -> Bool
allId f (I x) = f x
allId _ (C _) = True
allId f (F _ u) = all (allId f) u
allId f (G t) = all f (M.keys t)
allId f (D x) = f x
allId _ (P _) = True
-- Eliminate all trivial bindings so that an environment can be used
-- as a substitution.
nonTrivialEnv :: GenEnv -> GenEnv
nonTrivialEnv (g, Env (v, r)) =
(g, Env (v, M.filterWithKey nonTrivialBinding r))
{-
nonTrivialEnv :: GenEnv -> GenEnv
nonTrivialEnv (g, Env (v, r)) =
nonGroupEnv (M.assocs r) M.empty []
where
nonGroupEnv [] env grp =
groupEnv g v env grp grp
nonGroupEnv ((x, I y):r) env grp
| x == y = nonGroupEnv r env grp
nonGroupEnv ((x, G y):r) env grp
| isGroupVar y && varId (G y) == x =
nonGroupEnv r env grp
| otherwise = nonGroupEnv r env ((x, y):grp)
nonGroupEnv ((x, y):r) env grp = nonGroupEnv r (M.insert x y env) grp
groupEnv :: Gen -> Set Id -> IdMap -> [(Id, Group)] -> [(Id, Group)] -> GenEnv
groupEnv g v env grp [] =
(g, Env (v, foldl (\env (x, y) -> M.insert x (G y) env) env grp))
groupEnv g v env grp ((x, t):map)
| M.lookup x t /= Just 1 = groupEnv g v env grp map
| otherwise =
let (t0, t1) = partition M.empty (mul t (M.singleton x (-1))) v in
case matchGroup (group t0) (group t1) S.empty g of
Nothing -> groupEnv g v env grp map
Just (v', g', subst, _) ->
let grp' = L.delete (x, t) grp
grp'' = L.map (\(x, t) -> (x, groupSubst subst t)) grp' in
groupEnv g' (S.union v' v) env grp'' grp''
-}
-- Cast an environment into a substitution by filtering out trivial
-- bindings.
substitution :: Env -> Subst
substitution (Env (_, r)) =
Subst $ M.filterWithKey nonTrivialBinding r
-- Add type information to an environment, and return it as a list of
-- associations.
reify :: [Term] -> Env -> [(Term, Term)]
reify domain (Env (_, env)) =
map (loop domain) $ M.assocs env
where
loop [] (x, _) =
error $ "Algebra.reify: variable missing from domain " ++ idName x
loop (I x : _) (y, t)
| x == y = (I x, t)
loop (F Text [I x] : _) (y, t)
| x == y = (F Text [I x], F Text [t])
loop (F Data [I x] : _) (y, t)
| x == y = (F Data [I x], F Data [t])
loop (F Name [I x] : _) (y, t)
| x == y = (F Name [I x], F Name [t])
loop (F Skey [I x] : _) (y, t)
| x == y = (F Skey [I x], F Skey [t])
loop (F Akey [I x] : _) (y, t)
| x == y = (F Akey [I x], F Akey [t])
loop (F Base [I x] : _) (y, t)
| x == y = (F Base [I x], F Base [t])
loop (G x : _) (y, G t)
| isGroupVar x && varId (G x) == y = (G x, G t)
loop (D x : _) (y, t)
| x == y = (D x, t)
loop (_ : domain) pair = loop domain pair
-- Ensure the range of an environment contains only variables and that
-- the environment is injective.
{- matchRenaming :: GenEnv -> Bool
matchRenaming (_, Env (_, e)) =
loop S.empty $ M.elems e
where
loop _ [] = True
loop s (I x:e) =
S.notMember x s && loop (S.insert x s) e
loop s (G y:e) | isGroupVar y =
let x = getGroupVar y in
S.notMember x s && loop (S.insert x s) e
loop _ _ = False -}
matchRenaming :: GenEnv -> Bool
matchRenaming (gen, Env (v, e)) =
nonGrp S.empty (M.elems e) &&
groupMatchRenaming v gen (M.foldrWithKey grp M.empty e)
where
nonGrp _ [] = True
nonGrp s (I x:e) =
not (S.member x s) && nonGrp (S.insert x s) e
nonGrp s (G _:e) = nonGrp s e -- Check group bindings elsewhere
nonGrp _ _ = False
grp x (G t) map = M.insert x t map
grp _ _ map = map
-- For exponents, what we're looking for is an invertible
-- map. It doesn't have to be a strict renaming.
-- For instance x -> yz, w -> z is not a renaming, but z -> w, y -> w^{-1}x inverts it.
groupMatchRenaming :: Set Id -> Gen -> Map Id Group -> Bool
groupMatchRenaming v gen map =
loop S.empty $ M.elems map
where
loop _ [] = True
loop s (t:ge)
| M.null t = False
| isGroupVar t =
let x = varId (G t) in
not (S.member x s) && loop (S.insert x s) ge
| M.size t == 1 && snd (head (M.elems t)) == -1 =
let x = getGroupVar t in
not (S.member x s) && loop (S.insert x s) ge
| otherwise = any (groupMatchElim v gen map t) (M.assocs t)
groupMatchElim :: Set Id -> Gen -> Map Id Group -> Group -> (Id, (Bool, Int)) -> Bool
groupMatchElim v gen ge t (x, (be,1)) =
let (t0, t1) = partition M.empty (mul t (M.singleton x (be,-1))) v in
case matchGroup (group t0) (group t1) S.empty gen M.empty of
[] -> False
((v', gen', subst):_) ->
groupMatchRenaming (S.union v' v) gen' $ M.map (groupSubst subst) ge
groupMatchElim _ _ _ _ _ = False
nodeMatch :: Term -> (Int, Int) -> GenEnv -> [GenEnv]
nodeMatch t p env = match t (P p) env
nodeLookup :: Env -> Term -> Maybe (Int, Int)
nodeLookup env t =
case instantiate env t of
P p -> Just p
_ -> Nothing
instance C.Env Term Gen Subst Env where
emptyEnv = emptyEnv
instantiate = instantiate
match = match
identityEnvFor e ts = maybe [] (: []) $ identityEnvFor e ts
substitution = substitution
reify = reify
matchRenaming = matchRenaming
nodeMatch = nodeMatch
nodeLookup = nodeLookup
-- Term specific loading functions
loadVars :: Monad m => Gen -> [SExpr Pos] -> m (Gen, [Term])
loadVars gen sexprs =
do
pairs <- mapM loadVarPair sexprs
(g, vars) <- foldM loadVar (gen, []) (concat pairs)
return (g, reverse vars)
loadVarPair :: Monad m => SExpr Pos -> m [(SExpr Pos, SExpr Pos)]
loadVarPair (L _ (x:xs)) =
let (t:vs) = reverse (x:xs) in
return [(v,t) | v <- reverse vs]
loadVarPair x = fail (shows (annotation x) "Malformed vars declaration")
loadVar :: Monad m => (Gen, [Term]) -> (SExpr Pos, SExpr Pos) ->
m (Gen, [Term])
loadVar (gen, vars) (S pos name, S pos' sort) =
case loadLookup pos vars False name of
Right _ ->
fail (shows pos "Duplicate variable declaration for " ++ name)
Left _ ->
do
let (gen', x) = freshId gen name
p <- mkVar x
return (gen', p : vars)
where
mkVar x =
let t = I x in
case sort of
"mesg" -> return t
"text" -> return $ F Text [t]
"data" -> return $ F Data [t]
"name" -> return $ F Name [t]
"skey" -> return $ F Skey [t]
"akey" -> return $ F Akey [t]
"base" -> return $ F Base [t]
"expr" -> return $ groupVar False x
"expn" -> return $ groupVar True x
"node" -> return (D x)
_ -> fail (shows pos' "Sort " ++ sort ++ " not recognized")
loadVar _ (x,_) = fail (shows (annotation x) "Bad variable syntax")
loadLookup :: Pos -> [Term] -> Bool -> String -> Either String Term
loadLookup pos [] _ name = Left (shows pos $ "Identifier " ++ name ++ " unknown")
loadLookup pos (t@(G _) : u) True name =
let name' = idName (varId t) in
if name' == name then Left (shows pos $ "Disallowed bare exponent")
else loadLookup pos u True name
loadLookup pos (t : u) flag name =
let name' = idName (varId t) in
if name' == name then Right t else loadLookup pos u flag name
loadLookupName :: Monad m => Pos -> [Term] -> String -> m Term
loadLookupName pos vars name =
either fail f (loadLookup pos vars True name)
where
f t@(F Name [I _]) = return t
f _ = fail (shows pos $ "Expecting " ++ name ++ " to be a name")
loadLookupAkey :: Monad m => Pos -> [Term] -> String -> m Term
loadLookupAkey pos vars name =
either fail f (loadLookup pos vars True name)
where
f t@(F Akey [I _]) = return t
f _ = fail (shows pos $ "Expecting " ++ name ++ " to be an akey")
-- Load term and check that it is well-formed.
loadTerm :: Monad m => [Term] -> Bool -> SExpr Pos -> m Term
loadTerm vars strict (S pos s) =
either fail return (loadLookup pos vars strict s)
loadTerm _ _ (Q _ t) =
return (C t)
loadTerm vars strict (L pos (S _ s : l)) =
case lookup s loadDispatch of
Nothing -> fail (shows pos "Keyword " ++ s ++ " unknown")
Just f -> f pos strict vars l
loadTerm _ _ x = fail (shows (annotation x) "Malformed term")
type LoadFunction m = Pos -> Bool -> [Term] -> [SExpr Pos] -> m Term
loadDispatch :: Monad m => [(String, LoadFunction m)]
loadDispatch =
[("pubk", loadPubk)
,("privk", loadPrivk)
,("invk", loadInvk)
,("ltk", loadLtk)
,("bltk", loadBltk)
,("gen", loadGen)
,("exp", loadExp)
,("one", loadOne)
,("rec", loadRec)
,("mul", loadMul)
,("cat", loadCat)
,("enc", loadEnc)
,("hash", loadHash)
]
-- Atom constructors: pubk privk invk ltk
loadPubk :: Monad m => LoadFunction m
loadPubk _ _ vars [S pos s] =
do
t <- loadLookupName pos vars s
return $ F Akey [F Pubk [I $ varId t]]
loadPubk _ _ vars [Q _ c, S pos s] =
do
t <- loadLookupName pos vars s
return $ F Akey [F Pubk [C c, I $ varId t]]
loadPubk pos _ _ _ = fail (shows pos "Malformed pubk")
loadPrivk :: Monad m => LoadFunction m
loadPrivk _ _ vars [S pos s] =
do
t <- loadLookupName pos vars s
return $ F Akey [F Invk [F Pubk [I $ varId t]]]
loadPrivk _ _ vars [Q _ c, S pos s] =
do
t <- loadLookupName pos vars s
return $ F Akey [F Invk [F Pubk [C c, I $ varId t]]]
loadPrivk pos _ _ _ = fail (shows pos "Malformed privk")
loadInvk :: Monad m => LoadFunction m
loadInvk _ _ vars [S pos s] =
do
t <- loadLookupAkey pos vars s
return $ F Akey [F Invk [I $ varId t]]
loadInvk pos _ _ _ = fail (shows pos "Malformed invk")
loadLtk :: Monad m => LoadFunction m
loadLtk _ _ vars [S pos s, S pos' s'] =
do
t <- loadLookupName pos vars s
t' <- loadLookupName pos' vars s'
return $ F Skey [F Ltk [I $ varId t, I $ varId t']]
loadLtk pos _ _ _ = fail (shows pos "Malformed ltk")
loadBltk :: Monad m => LoadFunction m
loadBltk _ _ vars [S pos s, S pos' s'] =
do
t <- loadLookupName pos vars s
t' <- loadLookupName pos' vars s'
return $ F Skey [F Bltk [I $ varId t, I $ varId t']]
loadBltk pos _ _ _ = fail (shows pos "Malformed bltk")
-- Base and exponents
loadGen :: Monad m => LoadFunction m
loadGen _ _ _ [] =
return $ F Base [F Genr []]
loadGen pos _ _ _ = fail (shows pos "Malformed gen")
loadExp :: Monad m => LoadFunction m
loadExp _ _ vars [x, x'] =
do
t <- loadBase vars x
t' <- loadExpr vars False x'
return $ F Base [idSubst emptyIdMap $ F Exp [t, G t']]
loadExp pos _ _ _ = fail (shows pos "Malformed exp")
loadBase :: Monad m => [Term] -> SExpr Pos -> m Term
loadBase vars x =
do
t <- loadTerm vars False x
case t of
F Base [t] -> return t
_ -> fail (shows (annotation x) "Malformed base")
loadExpr :: Monad m => [Term] -> Bool -> SExpr Pos -> m Group
loadExpr vars False x =
do
t <- loadTerm vars False x
case t of
G t -> return t
_ -> fail (shows (annotation x) "Malformed expr")
loadExpr _ True x =
do
fail (shows (annotation x) "Disallowed bare exponent")
loadOne :: Monad m => LoadFunction m
loadOne _ False _ [] =
return $ G M.empty
loadOne pos True _ _ = fail (shows pos "Disallowed bare exponent")
loadOne pos _ _ _ = fail (shows pos "Malformed one")
loadRec :: Monad m => LoadFunction m
loadRec _ False vars [x] =
do
t <- loadExpr vars False x
return $ G $ invert t
loadRec pos True _ _ = fail (shows pos "Disallowed bare exponent")
loadRec pos _ _ _ = fail (shows pos "Malformed rec")
loadMul :: Monad m => LoadFunction m
loadMul _ False vars xs =
do
t <- foldM f M.empty xs
return $ G t
where
f acc x =
do
t <- loadExpr vars False x
return $ mul t acc
loadMul pos True _ _ = fail (shows pos "Disallowed bare exponent")
-- Term constructors: cat enc
loadCat :: Monad m => LoadFunction m
loadCat _ strict vars (l : ls) =
do
ts <- mapM (loadTerm vars strict) (l : ls)
return $ foldr1 (\a b -> F Cat [a, b]) ts
loadCat pos _ _ _ = fail (shows pos "Malformed cat")
loadEnc :: Monad m => LoadFunction m
loadEnc pos strict vars (l : l' : ls) =
do
let (butLast, last) = splitLast l (l' : ls)
t <- loadCat pos strict vars butLast
t' <- loadTerm vars strict last
return $ F Enc [t, t']
loadEnc pos _ _ _ = fail (shows pos "Malformed enc")
splitLast :: a -> [a] -> ([a], a)
splitLast x xs =
loop [] x xs
where
loop z x [] = (reverse z, x)
loop z x (y : ys) = loop (x : z) y ys
loadHash :: Monad m => LoadFunction m
loadHash _ strict vars (l : ls) =
do
ts <- mapM (loadTerm vars strict) (l : ls)
return $ F Hash [foldr1 (\a b -> F Cat [a, b]) ts]
loadHash pos _ _ _ = fail (shows pos "Malformed hash")
-- Term specific displaying functions
newtype Context = Context [(Id, String)] deriving Show
displayVars :: Context -> [Term] -> [SExpr ()]
displayVars _ [] = []
displayVars ctx vars =
let (v,t):pairs = map (displayVar ctx) vars in
loop t [v] pairs
where
loop t vs [] = [L () (reverse (t:vs))]
loop t vs ((v',t'):xs)
| t == t' = loop t (v':vs) xs
| otherwise = L () (reverse (t:vs)):loop t' [v'] xs
displayVar :: Context -> Term -> (SExpr (), SExpr ())
displayVar ctx (I x) = displaySortId "mesg" ctx x
displayVar ctx (F Text [I x]) = displaySortId "text" ctx x
displayVar ctx (F Data [I x]) = displaySortId "data" ctx x
displayVar ctx (F Name [I x]) = displaySortId "name" ctx x
displayVar ctx (F Skey [I x]) = displaySortId "skey" ctx x
displayVar ctx (F Akey [I x]) = displaySortId "akey" ctx x
displayVar ctx (F Base [I x]) = displaySortId "base" ctx x
displayVar ctx t@(G x)
| isBasisVar x = displaySortId "expn" ctx (varId t)
| isGroupVar x = displaySortId "expr" ctx (varId t)
displayVar ctx (D x) = displaySortId "node" ctx x
displayVar _ _ =
C.assertError "Algebra.displayVar: term not a variable with its sort"
displaySortId :: String -> Context -> Id -> (SExpr (), SExpr ())
displaySortId sort ctx x = (displayId ctx x, S () sort)
displayId :: Context -> Id -> SExpr ()
displayId (Context ctx) x =
case lookup x ctx of
Nothing ->
let msg = idName x ++ " in a display context" in
error $ "Algebra.displayId: Cannot find variable " ++ msg
Just name -> S () name
displayTerm :: Context -> Term -> SExpr ()
displayTerm ctx (I x) = displayId ctx x
displayTerm ctx (F Text [I x]) = displayId ctx x
displayTerm ctx (F Data [I x]) = displayId ctx x
displayTerm ctx (F Name [I x]) = displayId ctx x
displayTerm ctx (F Skey [I x]) = displayId ctx x
displayTerm ctx (F Skey [F Ltk [I x, I y]]) =
L () [S () "ltk", displayId ctx x, displayId ctx y]
displayTerm ctx (F Skey [F Bltk [I x, I y]])
| x > y = displayTerm ctx (F Skey [F Bltk [I y, I x]])
| otherwise = L () [S () "bltk", displayId ctx x, displayId ctx y]
displayTerm ctx (F Akey [t]) =
case t of
I x -> displayId ctx x
F Invk [I x] -> L () [S () "invk", displayId ctx x]
F Pubk [I x] -> L () [S () "pubk", displayId ctx x]
F Pubk [C c, I x] -> L () [S () "pubk", Q () c, displayId ctx x]
F Invk [F Pubk [I x]] -> L () [S () "privk", displayId ctx x]
F Invk [F Pubk [C c, I x]] ->
L () [S () "privk", Q () c, displayId ctx x]
_ -> error ("Algebra.displayAkey: Bad term " ++ show t)
displayTerm ctx (F Base [t]) =
displayBase t
where
displayBase (I x) = displayId ctx x
displayBase (F Genr []) =
L () [S () "gen"]
displayBase (F Exp [t0, G t1]) =
L () [S () "exp", displayBase t0, displayTerm ctx (G t1)]
displayBase t = error ("Algebra.displayBase: Bad term " ++ show t)
displayTerm ctx (G t) =
displayExpn t
where
displayExpn t
| M.null t = L () [S () "one"]
| otherwise =
case factors t of
[f] -> displayFactor f
fs -> L () (S () "mul" : map displayFactor fs)
displayFactor (x, (_, n))
| n >= 0 = displayId ctx x
| otherwise = L () [S () "rec", displayId ctx x]
displayTerm _ (C t) = Q () t
displayTerm ctx (F Cat [t0, t1]) =
L () (S () "cat" : displayTerm ctx t0 : displayList ctx t1)
displayTerm ctx (F Enc [t0, t1]) =
L () (S () "enc" : displayEnc ctx t0 t1)
displayTerm ctx (F Hash [t]) =
L () (S () "hash" : displayList ctx t)
displayTerm ctx (D x) = displayId ctx x
displayTerm _ (P (z, i)) = L () [N () z, N () i]
displayTerm _ t = error ("Algebra.displayTerm: Bad term " ++ show t)
displayList :: Context -> Term -> [SExpr ()]
displayList ctx (F Cat [t0, t1]) = displayTerm ctx t0 : displayList ctx t1
displayList ctx t = [displayTerm ctx t]
displayEnc :: Context -> Term -> Term -> [SExpr ()]
displayEnc ctx (F Cat [t0, t1]) t = displayTerm ctx t0 : displayEnc ctx t1 t
displayEnc ctx t0 t1 = [displayTerm ctx t0, displayTerm ctx t1]
displayEnv :: Context -> Context -> Env -> [SExpr ()]
displayEnv ctx ctx' (Env (_, r)) =
map (\(x, t) -> L () [displayTerm ctx x, displayTerm ctx' t]) r'
where
r' = map (\(x, t) -> (I x, inferSort t)) $ M.assocs r
-- displaySubst c s displays a substitution s in context c, where some
-- variables that occur in s might not be in c. Enough sort
-- inference is performed so as to allow the extension of the context.
displaySubst :: Context -> Subst -> [SExpr ()]
displaySubst ctx s@(Subst r) =
map (\(x, t) -> L () [displayTerm ctx' x, displayTerm ctx' t]) r'
where
r' = map (\(x, t) -> (I x, inferSort (substitute s t))) $ M.assocs r
ctx' = foldl (\ctx (x, t) -> addToContext ctx [x, t]) ctx r'
inferSort :: Term -> Term
inferSort t@(F Invk _) = F Akey [t]
inferSort t@(F Pubk _) = F Akey [t]
inferSort t@(F Ltk _) = F Skey [t]
inferSort t@(F Bltk _) = F Skey [t]
inferSort t@(F Genr _) = F Base [t]
inferSort t@(F Exp _) = F Base [t]
inferSort t = t
emptyContext :: Context
emptyContext = Context []
-- Generate names for output renaming as necessary.
-- Assumes the input is a list of term that are well-formed
addToContext :: Context -> [Term] -> Context
addToContext ctx u =
foldl (foldVars varContext) ctx u
varContext :: Context -> Term -> Context
varContext ctx t =
let x = varId t
name = rootName $ idName x in
if hasId ctx x then
ctx
else
if hasName ctx name then
extendContext ctx x (genName ctx name)
else
extendContext ctx x name
hasId :: Context -> Id -> Bool
hasId (Context ctx) id =
maybe False (const True) (lookup id ctx)
hasName :: Context -> String -> Bool
hasName (Context ctx) name =
maybe False (const True) (L.find ((name ==) . snd) ctx)
extendContext :: Context -> Id -> String -> Context
extendContext (Context ctx) x name =
Context $ (x, name) : ctx
genName :: Context -> String -> String
genName ctx name =
loop 0
where
root = '-' : reverse name
loop :: Int -> String
loop n =
let name' = revapp root (show n) in
if hasName ctx name' then
loop (n + 1)
else
name'
revapp [] s = s
revapp (c : cs) s = revapp cs (c : s)
rootName :: String -> String
rootName name =
noHyphen 0 name
where
noHyphen _ [] = name
noHyphen i (c : s)
| c == '-' = hyphen i (i + 1) s
| otherwise = noHyphen (i + 1) s
hyphen i _ [] = rootName $ take i name
hyphen i j (c : s)
| isDigit c = hyphen i (j + 1) s
| otherwise = noHyphen j (c : s)
instance C.Context Term Gen Subst Env Context where
emptyContext = emptyContext
addToContext = addToContext
displayVars = displayVars
displayTerm = displayTerm
displayEnv = displayEnv
displaySubst = displaySubst
instance C.Algebra Term Place Gen Subst Env Context