Skip to content

Commit

Permalink
148/more cleanup (WIP) (#150)
Browse files Browse the repository at this point in the history
* Some cleanup and explicit typing. 148.

* Move modules to Language.CQL. #148

* Clean up Program parsers. #148

* Clean up Program parsers. #148

* Move into Language.CQL.Parser.Program.

* Simplify some of the stuff inside.

* Move QuickCheck generator for Parser into test dir. #148

* More cleanup. #148

* Segregate morphism code into Morphism.hs. #148

* Tighten imports from Morphism. #148

* Rename Morphism-related functions for clarity. #148

* Improve naming and qualification of Morphism-related functions. #148

* Move Collage into its own separate module. #148

* Fix typo in 'lower' doc comment. #148
  • Loading branch information
epost authored Aug 15, 2019
1 parent c8cc768 commit d911bab
Show file tree
Hide file tree
Showing 31 changed files with 1,198 additions and 1,053 deletions.
48 changes: 24 additions & 24 deletions src/Language/CQL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,26 @@ module Language.CQL where
import Control.Concurrent
import Control.DeepSeq
import Control.Exception
import Data.List (nub)
import qualified Data.Map.Strict as Map
import Data.List (nub)
import qualified Data.Map.Strict as Map
import Data.Maybe
import Data.Typeable
import Language.Common as C
import Language.Graph
import Language.Instance as I
import Language.Mapping as M
import Language.Options
import Language.Parser (parseCqlProgram)
import Language.Program as P
import Language.Query as Q
import Language.Schema as S
import Language.Term as Term
import Language.Transform as Tr
import Language.Typeside as T
import Prelude hiding (EQ, exp)
import Language.CQL.Common as C
import Language.CQL.Graph
import Language.CQL.Instance as I
import Language.CQL.Mapping as M
import Language.CQL.Options
import Language.CQL.Parser.Program (parseProgram)
import Language.CQL.Program as P
import Language.CQL.Query as Q
import Language.CQL.Schema as S
import Language.CQL.Term as Term
import Language.CQL.Transform as Tr
import Language.CQL.Typeside as T
import Prelude hiding (EQ, exp)
import System.IO.Unsafe

-- | Timesout a computation after @i@ microseconds.
-- | Times out a computation after @i@ microseconds.
timeout' :: NFData x => Integer -> Err x -> Err x
timeout' i p = unsafePerformIO $ do
m <- newEmptyMVar
Expand Down Expand Up @@ -240,15 +240,15 @@ typecheckSchemaExp p x = case x of
-- | The result of evaluating an CQL program.
type Env = KindCtx TypesideEx SchemaEx InstanceEx MappingEx QueryEx TransformEx Options

-- | Simple three phase evaluation and reporting.
-- | Parse, typecheck, and evaluate the CQL program.
runProg :: String -> Err (Prog, Types, Env)
runProg p = do
p1 <- parseCqlProgram p
ops <- toOptions defaultOptions $ other p1
o <- findOrder p1
p2 <- typecheckCqlProgram o p1 newTypes
p3 <- evalCqlProgram o p1 $ newEnv ops
return (p1, p2, p3)
runProg srcText = do
progE <- parseProgram srcText
opts <- toOptions defaultOptions $ other progE
o <- findOrder progE
typesE <- typecheckCqlProgram o progE newTypes
envE <- evalCqlProgram o progE $ newEnv opts
return (progE, typesE, envE)

evalCqlProgram :: [(String,Kind)] -> Prog -> Env -> Err Env
evalCqlProgram [] _ env = pure env
Expand Down
226 changes: 226 additions & 0 deletions src/Language/CQL/Collage.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
{-
SPDX-License-Identifier: AGPL-3.0-only
This file is part of `statebox/cql`, the categorical query language.
Copyright (C) 2019 Stichting Statebox <https://statebox.nl>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE IncoherentInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LiberalTypeSynonyms #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}

module Language.CQL.Collage where

import Control.DeepSeq (NFData)
import Data.Map.Merge.Strict
import Data.Map.Strict as Map hiding (foldr, size)
import Data.Set as Set hiding (foldr, size)
import Data.Void
import Language.CQL.Common
import Language.CQL.Term (Head(..), Term(..), simplifyFix, occsTerm, upp)
import Language.CQL.Term (EQ(..), Ctx)
import Prelude hiding (EQ)

data Collage var ty sym en fk att gen sk
= Collage
{ ceqs :: Set (Ctx var (ty+en), EQ var ty sym en fk att gen sk)
, ctys :: Set ty
, cens :: Set en
, csyms :: Map sym ([ty],ty)
, cfks :: Map fk (en, en)
, catts :: Map att (en, ty)
, cgens :: Map gen en
, csks :: Map sk ty
} deriving (Eq, Show)

--------------------------------------------------------------------------------

occs
:: (Ord sym, Ord fk, Ord att, Ord gen, Ord sk)
=> Collage var ty sym en fk att gen sk
-> Map (Head ty sym en fk att gen sk) Int
occs col = foldr (\(_, EQ (lhs, rhs)) x -> m x $ m (occsTerm lhs) $ occsTerm rhs) Map.empty $ ceqs col
where
m = merge preserveMissing preserveMissing $ zipWithMatched (\_ x y -> x + y)

--------------------------------------------------------------------------------

-- | Simplify a collage by replacing symbols of the form @gen/sk = term@, yielding also a
-- translation function from the old theory to the new, encoded as a list of (symbol, term) pairs.
simplify
:: (MultiTyMap '[Show, Ord, NFData] '[var, ty, sym, en, fk, att, gen, sk])
=> Collage var ty sym en fk att gen sk
-> (Collage var ty sym en fk att gen sk, [(Head ty sym en fk att gen sk, Term var ty sym en fk att gen sk)])
simplify (Collage ceqs' ctys' cens' csyms' cfks' catts' cgens' csks' )
= (Collage ceqs'' ctys' cens' csyms' cfks' catts' cgens'' csks'', f)
where
(ceqs'', f) = simplifyFix ceqs' []
cgens'' = Map.fromList $ Prelude.filter (\(x,_) -> notElem (HGen x) $ fmap fst f) $ Map.toList cgens'
csks'' = Map.fromList $ Prelude.filter (\(x,_) -> notElem (HSk x) $ fmap fst f) $ Map.toList csks'

--------------------------------------------------------------------------------

eqsAreGround :: Collage var ty sym en fk att gen sk -> Bool
eqsAreGround col = Prelude.null [ x | x <- Set.toList $ ceqs col, not $ Map.null (fst x) ]

fksFrom :: Eq en => Collage var ty sym en fk att gen sk -> en -> [(fk,en)]
fksFrom sch en' = f $ Map.assocs $ cfks sch
where f [] = []
f ((fk,(en1,t)):l) = if en1 == en' then (fk,t) : (f l) else f l

attsFrom :: Eq en => Collage var ty sym en fk att gen sk -> en -> [(att,ty)]
attsFrom sch en' = f $ Map.assocs $ catts sch
where f [] = []
f ((fk,(en1,t)):l) = if en1 == en' then (fk,t) : (f l) else f l

-- | Gets the type of a term that is already known to be well-typed.
typeOf
:: (MultiTyMap '[Show, Ord, NFData] '[var, ty, sym, en, fk, att, gen, sk])
=> Collage var ty sym en fk att gen sk
-> Term Void Void Void en fk Void gen Void
-> en
typeOf col e = case typeOf' col Map.empty (upp e) of
Left _ -> error "Impossible in typeOf, please report."
Right x -> case x of
Left _ -> error "Impossible in typeOf, please report."
Right y -> y

checkDoms
:: (MultiTyMap '[Show, Ord, NFData] '[var, ty, sym, en, fk, att, gen, sk])
=> Collage var ty sym en fk att gen sk
-> Err ()
checkDoms col = do
mapM_ f $ Map.elems $ csyms col
mapM_ g $ Map.elems $ cfks col
mapM_ h $ Map.elems $ catts col
mapM_ isEn $ Map.elems $ cgens col
mapM_ isTy $ Map.elems $ csks col
where
f (t1,t2) = do { mapM_ isTy t1 ; isTy t2 }
g (e1,e2) = do { isEn e1 ; isEn e2 }
h (e ,t ) = do { isEn e ; isTy t }
isEn x = if Set.member x $ cens col
then pure ()
else Left $ "Not an entity: " ++ show x
isTy x = if Set.member x $ ctys col
then pure ()
else Left $ "Not a type: " ++ show x

typeOfCol
:: (MultiTyMap '[Show, Ord, NFData] '[var, ty, sym, en, fk, att, gen, sk])
=> Collage var ty sym en fk att gen sk
-> Err ()
typeOfCol col = do
checkDoms col
mapM_ (typeOfEq' col) $ Set.toList $ ceqs col

--------------------------------------------------------------------------------

typeOf'
:: (MultiTyMap '[Show, Ord, NFData] '[var, ty, sym, en, fk, att, gen, sk])
=> Collage var ty sym en fk att gen sk
-> Ctx var (ty + en)
-> Term var ty sym en fk att gen sk
-> Err (ty + en)
typeOf' _ ctx (Var v) = note ("Unbound variable: " ++ show v) $ Map.lookup v ctx
typeOf' col _ (Gen g) = case Map.lookup g $ cgens col of
Nothing -> Left $ "Unknown generator: " ++ show g
Just t -> Right $ Right t
typeOf' col _ (Sk s) = case Map.lookup s $ csks col of
Nothing -> Left $ "Unknown labelled null: " ++ show s
Just t -> Right $ Left t
typeOf' col ctx xx@(Fk f a) = case Map.lookup f $ cfks col of
Nothing -> Left $ "Unknown foreign key: " ++ show f
Just (s, t) -> do s' <- typeOf' col ctx a
if (Right s) == s' then pure $ Right t else Left $ "Expected argument to have entity " ++
show s ++ " but given " ++ show s' ++ " in " ++ (show xx)
typeOf' col ctx xx@(Att f a) = case Map.lookup f $ catts col of
Nothing -> Left $ "Unknown attribute: " ++ show f
Just (s, t) -> do s' <- typeOf' col ctx a
if (Right s) == s' then pure $ Left t else Left $ "Expected argument to have entity " ++
show s ++ " but given " ++ show s' ++ " in " ++ (show xx)
typeOf' col ctx xx@(Sym f a) = case Map.lookup f $ csyms col of
Nothing -> Left $ "Unknown function symbol: " ++ show f
Just (s, t) -> do s' <- mapM (typeOf' col ctx) a
if length s' == length s
then if (Left <$> s) == s'
then pure $ Left t
else Left $ "Expected arguments to have types " ++
show s ++ " but given " ++ show s' ++ " in " ++ (show $ xx)
else Left $ "Expected argument to have arity " ++
show (length s) ++ " but given " ++ show (length s') ++ " in " ++ (show $ xx)

typeOfEq'
:: (MultiTyMap '[Show, Ord, NFData] '[var, ty, sym, en, fk, att, gen, sk])
=> Collage var ty sym en fk att gen sk
-> (Ctx var (ty + en), EQ var ty sym en fk att gen sk)
-> Err (ty + en)
typeOfEq' col (ctx, EQ (lhs, rhs)) = do
lhs' <- typeOf' col ctx lhs
rhs' <- typeOf' col ctx rhs
if lhs' == rhs'
then Right lhs'
else Left $ "Equation lhs has type " ++ show lhs' ++ " but rhs has type " ++ show rhs'

--------------------------------------------------------------------------------

-- | Initialize a mapping of sorts to bools for sort inhabition check.
initGround :: (Ord ty, Ord en) => Collage var ty sym en fk att gen sk -> (Map en Bool, Map ty Bool)
initGround col = (me', mt')
where
me = Map.fromList $ Prelude.map (\en -> (en, False)) $ Set.toList $ cens col
mt = Map.fromList $ Prelude.map (\ty -> (ty, False)) $ Set.toList $ ctys col
me' = Prelude.foldr (\(_, en) m -> Map.insert en True m) me $ Map.toList $ cgens col
mt' = Prelude.foldr (\(_, ty) m -> Map.insert ty True m) mt $ Map.toList $ csks col

-- | Applies one layer of symbols to the sort to boolean inhabitation map.
closeGround :: (Ord ty, Ord en) => Collage var ty sym en fk att gen sk -> (Map en Bool, Map ty Bool) -> (Map en Bool, Map ty Bool)
closeGround col (me, mt) = (me', mt'')
where
mt''= Prelude.foldr (\(_, (tys,ty)) m -> if and ((!) mt' <$> tys) then Map.insert ty True m else m) mt' $ Map.toList $ csyms col
mt' = Prelude.foldr (\(_, (en, ty)) m -> if (!) me' en then Map.insert ty True m else m) mt $ Map.toList $ catts col
me' = Prelude.foldr (\(_, (en, _ )) m -> if (!) me en then Map.insert en True m else m) me $ Map.toList $ cfks col

-- | Does a fixed point of closeGround.
iterGround :: (MultiTyMap '[Show, Ord, NFData] '[ty, en]) => Collage var ty sym en fk att gen sk -> (Map en Bool, Map ty Bool) -> (Map en Bool, Map ty Bool)
iterGround col r = if r == r' then r else iterGround col r'
where r' = closeGround col r

-- | Gets the inhabitation map for the sorts of a collage.
computeGround :: (MultiTyMap '[Show, Ord, NFData] '[ty, en]) => Collage var ty sym en fk att gen sk -> (Map en Bool, Map ty Bool)
computeGround col = iterGround col $ initGround col

-- | True iff all sorts in a collage are inhabited.
allSortsInhabited :: (MultiTyMap '[Show, Ord, NFData] '[ty, en]) => Collage var ty sym en fk att gen sk -> Bool
allSortsInhabited col = t && f
where (me, mt) = computeGround col
t = and $ Map.elems me
f = and $ Map.elems mt
29 changes: 18 additions & 11 deletions src/Language/Common.hs → src/Language/CQL/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}

module Language.Common where
module Language.CQL.Common where

import Control.Arrow (left)
import Data.Char
Expand All @@ -65,15 +65,16 @@ fromListAccum ((k,v):kvs) = Map.insert k op (fromListAccum kvs)
op = maybe (Set.singleton v) (Set.insert v) (Map.lookup k r)
r = fromListAccum kvs

fromList'' :: (Show k, Ord k) => [k] -> Err (Set k)
fromList'' [] = return Set.empty
fromList'' (k:l) = do
l' <- fromList'' l
-- | Converts a 'List' to a 'Set', returning an error when there are duplicate bindings.
toSetSafely :: (Show k, Ord k) => [k] -> Err (Set k)
toSetSafely [] = return Set.empty
toSetSafely (k:l) = do
l' <- toSetSafely l
if Set.member k l'
then Left $ "Duplicate binding: " ++ show k
else pure $ Set.insert k l'

-- | Converts a map to a finite list, returning an error when there are duplicate bindings.
-- | Converts an association list to a 'Map', returning an error when there are duplicate bindings.
toMapSafely :: (Show k, Ord k) => [(k,v)] -> Err (Map k v)
toMapSafely [] = return Map.empty
toMapSafely ((k,v):l) = do
Expand All @@ -100,12 +101,14 @@ note :: b -> Maybe a -> Either b a
note n = maybe (Left n) Right

data Kind = CONSTRAINTS | TYPESIDE | SCHEMA | INSTANCE | MAPPING | TRANSFORM | QUERY | COMMAND | GRAPH | COMMENT | SCHEMA_COLIMIT
deriving (Show, Eq, Ord)
deriving (Show, Eq, Ord)

type ID = Integer

sepTup :: (Show a1, Show a2) => String -> (a1, a2) -> String
sepTup sep (k,v) = show k ++ sep ++ show v
-- | Drop quotes if argument doesn't contain a space.
dropQuotes :: String -> String
dropQuotes s = if ' ' `elem` s then Prelude.filter (not . ('\"' ==)) s
else s

section :: String -> String -> String
section heading body = heading ++ "\n" ++ indentLines body
Expand All @@ -116,6 +119,9 @@ indentLines = foldMap (\l -> tab <> l <> "\n"). lines
tab :: String
tab = " "

sepTup :: (Show a1, Show a2) => String -> (a1, a2) -> String
sepTup sep (k,v) = show k ++ sep ++ show v

-- | A version of intercalate that works on Foldables instead of just List,
-- | adapted from PureScript.
intercalate :: (Foldable f, Monoid m) => m -> f m -> m
Expand All @@ -127,8 +133,9 @@ intercalate sep xs = snd (foldl go (True, mempty) xs)
mapl :: Foldable f => (a -> b) -> f a -> [b]
mapl fn = fmap fn . Foldable.toList

toLowercase :: String -> String
toLowercase = Prelude.map toLower
-- | Converts a String to lowercase, like Data.List.Extra.lower.
lower :: String -> String
lower = fmap toLower

-- | Heterogenous membership in a list
elem' :: (Typeable t, Typeable a, Eq a) => t -> [a] -> Bool
Expand Down
4 changes: 2 additions & 2 deletions src/Language/Congruence.hs → src/Language/CQL/Congruence.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE FlexibleContexts, OverloadedLists, OverloadedStrings, TupleSections #-}
module Language.Congruence (decide, Term(Function)) where
module Language.CQL.Congruence (decide, Term(Function)) where

import Prelude hiding (any)

Expand All @@ -35,7 +35,7 @@ import Data.Foldable (traverse_)
import Data.Graph.Inductive (LNode)
import Data.Functor.Identity

import Language.Internal
import Language.CQL.Internal


decide :: Ord t => [(Term t, Term t)] -> Term t -> Term t -> Bool
Expand Down
2 changes: 1 addition & 1 deletion src/Language/Graph.hs → src/Language/CQL/Graph.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-}

module Language.Graph where
module Language.CQL.Graph where

import Prelude

Expand Down
Loading

0 comments on commit d911bab

Please sign in to comment.