narc 0.1 → 0.1.1
raw patch · 43 files changed
+2126/−2141 lines, 43 filesdep ~base
Dependency ranges changed: base
Files
- Database/Narc.hs +166/−0
- Database/Narc/AST.hs +298/−0
- Database/Narc/AST/Pretty.hs +33/−0
- Database/Narc/Common.hs +6/−0
- Database/Narc/Compile.hs +226/−0
- Database/Narc/Contract.hs +7/−0
- Database/Narc/Debug.hs +31/−0
- Database/Narc/Eval.hs +119/−0
- Database/Narc/Failure.hs +46/−0
- Database/Narc/Failure/QuickCheck.hs +17/−0
- Database/Narc/HDBC.hs +17/−0
- Database/Narc/Pretty.hs +8/−0
- Database/Narc/Rewrite.hs +67/−0
- Database/Narc/SQL.hs +155/−0
- Database/Narc/SQL/Pretty.hs +39/−0
- Database/Narc/TermGen.hs +196/−0
- Database/Narc/Test.hs +82/−0
- Database/Narc/Type.hs +245/−0
- Database/Narc/TypeInfer.hs +233/−0
- Database/Narc/Util.hs +111/−0
- Database/Narc/Var.hs +4/−0
- Narc.hs +0/−200
- Narc/AST.hs +0/−298
- Narc/AST/Pretty.hs +0/−33
- Narc/Common.hs +0/−6
- Narc/Compile.hs +0/−226
- Narc/Contract.hs +0/−7
- Narc/Debug.hs +0/−31
- Narc/Eval.hs +0/−119
- Narc/Failure.hs +0/−46
- Narc/Failure/QuickCheck.hs +0/−17
- Narc/HDBC.hs +0/−13
- Narc/Pretty.hs +0/−8
- Narc/Rewrite.hs +0/−67
- Narc/SQL.hs +0/−155
- Narc/SQL/Pretty.hs +0/−39
- Narc/TermGen.hs +0/−196
- Narc/Test.hs +0/−82
- Narc/Type.hs +0/−245
- Narc/TypeInfer.hs +0/−233
- Narc/Util.hs +0/−111
- Narc/Var.hs +0/−4
- narc.cabal +20/−5
+ Database/Narc.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}++-- | Query SQL databases using Nested Relational Calculus embedded in+-- Haskell.+-- +-- The primed functions in this module are in fact the syntactic +-- forms of the embedded language. Use them as, for example:+-- +-- > foreach (table "employees" []) $ \emp ->+-- > having (primApp "<" [cnst 20000, project emp "salary"]) $+-- > singleton (record [(project emp "name")])++module Database.Narc (+ -- * The type of the embedded terms+ NarcTerm,+ -- * Translation to an SQL representation+ narcToSQL, narcToSQLString,+ -- * The language itself+ unit, table, cnst, primApp, abs, app, ifthenelse, singleton,+ nil, union, record, project, foreach, having,+ Type(..)+) where++import Prelude hiding (abs, catch)+import Control.Exception (catch, throwIO, evaluate, SomeException)+import Control.Monad.State hiding (when, join)+import Control.Monad.Error (throwError, runErrorT, Error(..))+import Data.List (nub, (\\), sort, sortBy, groupBy, intersperse)+import Data.Maybe (fromJust, isJust, fromMaybe)++import Control.Applicative ((<$>), (<*>))+import Foreign (unsafePerformIO) -- FIXME++import Test.QuickCheck hiding (promote, Failure)+import QCUtils+import Test.HUnit hiding (State, assert)++import Debug.Trace++import Gensym++import Database.Narc.AST+import Database.Narc.Common+import Database.Narc.Compile+import Database.Narc.Debug+import Database.Narc.Eval+import Database.Narc.Failure+import Database.Narc.Pretty+import Database.Narc.AST.Pretty+import Database.Narc.SQL.Pretty+import qualified Database.Narc.SQL as SQL+import Database.Narc.Type as Type+import Database.Narc.TypeInfer+import Database.Narc.Util++import Database.Narc.HDBC++-- THE AWESOME FULL COMPILATION FUNCTION -------------------------------++typeCheckAndCompile :: Term a -> SQL.Query+typeCheckAndCompile = compile [] . runTyCheck []++-- The Narc embedded langauge-------------------------------------------++-- Example query++example_dull = (Comp "x" (Table "foo" [("a", TBool)], ())+ (If (Project (Var "x", ()) "a", ())+ (Singleton (Var "x", ()), ())+ (Nil, ()), ()), ())++-- HOAS-ish embedded language.++type NarcTerm = Gensym (Term ())++-- | Translate a Narc term to an SQL query string--perhaps the central+-- | function of the interface.+narcToSQLString :: NarcTerm -> String+narcToSQLString = SQL.serialize . narcToSQL++-- | Translate a Narc term to an SQL query.+narcToSQL :: NarcTerm -> SQL.Query+narcToSQL = typeCheckAndCompile . realize++-- | Turn a HOAS representation of a Narc term into a concrete,+-- | named-binder representation.+realize :: NarcTerm -> Term ()+realize = runGensym++-- | A dummy value, or zero-width record.+unit :: NarcTerm+unit = return $ (!) Unit++-- | A polymorphic way of embedding constants into a term.+class Const' a where cnst :: a -> NarcTerm+instance Const' Bool where cnst b = return ((!)(Bool b))+instance Const' Integer where cnst n = return ((!)(Num n))++-- | Apply some primitive function, such as @(+)@ or @avg@, to a list+-- of arguments.+primApp :: String -> [NarcTerm] -> NarcTerm+primApp f args = (!) . PrimApp f <$> sequence args++-- | Create a functional abstraction.+abs :: (String -> NarcTerm) -> NarcTerm+abs fn = do+ n <- gensym+ let x = '_' : show n+ body <- fn x+ return $ (!) $ Abs x body++-- | Apply a functional term to an argument.+app :: NarcTerm -> NarcTerm -> NarcTerm+app l m = (!) <$> (App <$> l <*> m)++-- | A reference to a named database table; second argument is its+-- schema type.+table :: Tabname -> [(Field, Type)] -> NarcTerm+table tbl ty = return $ (!) $ Table tbl ty++-- | A condition between two terms, as determined by the boolean value+-- of the first term.+ifthenelse :: NarcTerm -> NarcTerm -> NarcTerm -> NarcTerm+ifthenelse c t f = (!) <$> (If <$> c <*> t <*> f)++-- | A singleton collection of one item.+singleton :: NarcTerm -> NarcTerm+singleton x = (!) . Singleton <$> x++-- | An empty collection.+nil :: NarcTerm+nil = return $ (!) $ Nil++-- | The union of two collections+union :: NarcTerm -> NarcTerm -> NarcTerm+union l r = (!) <$> (Union <$> l <*> r)++-- | Construct a record (name-value pairs) out of other terms; usually+-- used, with base values for the record elements, as the final+-- result of a query, corresponding to the @select@ clause of a SQL+-- query, but can also be used with nested results internally in a+-- query.+record :: [(String, NarcTerm)] -> NarcTerm+record fields = (!) <$> (Record <$> sequence [do expr' <- expr ; return (lbl, expr') | (lbl, expr) <- fields])++-- | Project a field out of a record value.+project :: NarcTerm -> String -> NarcTerm+project expr field = (!) <$> (Project <$> expr <*> return field)++-- | For each item in the collection resulting from the first+-- argument, give it to the function which is the second argument+-- and evaluate--this corresponds to a loop, or two one part of a+-- cross in traditional SQL queries.+foreach :: NarcTerm -> (NarcTerm -> NarcTerm) -> NarcTerm+foreach src k = do+ src' <- src+ n <- gensym+ let x = '_' : show n+ body' <- k (return (var_ x))+ return $ (!)(Comp x src' body')++-- | Filter the current iteration as per the condition in the first+-- argument. Corresponds to a @where@ clause in a SQL query.+having :: NarcTerm -> NarcTerm -> NarcTerm+having cond body = ifthenelse cond body nil
+ Database/Narc/AST.hs view
@@ -0,0 +1,298 @@+{-# LANGUAGE FlexibleInstances #-}++module Database.Narc.AST (+ Term'(..),+ Term,+ Var,+ PlainTerm,+ TypedTerm,+ fvs,+ substTerm,+ strip,+ retagulate,+ rename,+ variables,+ (!),+ unit_, Const, cnst_, primApp_, var_, abs_, app_, table_, ifthenelse_,+ singleton_, nil_, union_, record_, project_, foreach_ +) where++import Data.List as List ((\\), nub)++import Prelude hiding (abs)++import Database.Narc.Common+import Database.Narc.Type+import Database.Narc.Util (alistmap, u)+import Database.Narc.Var++-- | Terms in the nested relational calculus (represented concretely+-- | with named variables)+data Term' a = Unit | Bool Bool | Num Integer | String String+ | PrimApp String [Term a]+ | Var Var | Abs Var (Term a) | App (Term a) (Term a)+ | Table Tabname [(Field, Type)]+ | If (Term a) (Term a) (Term a)+ | Singleton (Term a) | Nil | Union (Term a) (Term a)+ | Record [(String, Term a)]+ | Project (Term a) String+ | Comp Var (Term a) (Term a)+-- | IsEmpty (Term a)+ deriving (Eq,Show)++-- | Terms whose every subexpression is annotated with a value of some+-- | particular type.+type Term a = (Term' a, a)++-- TBD: use term ::: type or similar instead of (term, type).++type PlainTerm = Term ()++type TypedTerm = Term Type++-- Operations on terms -------------------------------------------------++fvs (Unit, _) = []+fvs (Bool _, _) = []+fvs (Num _, _) = []+fvs (String _, _) = []+fvs (PrimApp prim args, _) = nub $ concat $ map fvs args+fvs (Var x, _) = [x]+fvs (Abs x n, _) = fvs n \\ [x]+fvs (App l m, _) = fvs l `u` fvs m+fvs (Table _ _, _) = []+fvs (If c a b, _) = fvs c `u` fvs a `u` fvs b+fvs (Nil, _) = []+fvs (Singleton elem, _) = fvs elem+fvs (Union m n, _) = fvs m `u` fvs n+fvs (Record fields, _) = nub $ concat $ map (fvs . snd) fields+fvs (Project targ _, _) = fvs targ+fvs (Comp x src body, _) = fvs src `u` (fvs body \\ [x])++variables = map ('y':) $ map show [0..]++rename x y (Var z, q) | x == z = (Var y, q)+ | otherwise = (Var z, q)+rename x y (l@(Abs z n, q)) | x == z = l+ | otherwise = (Abs z (rename x y n), q)+rename x y (App l m, q) = (App (rename x y l) (rename x y m), q)+rename x y (PrimApp prim args, q) = (PrimApp prim (map (rename x y) args), q)+rename x y (Singleton elem, q) = (Singleton (rename x y elem), q)+rename x y (Project targ label, q) = (Project (rename x y targ) label, q)+rename x y (Record fields, q) = (Record (alistmap (rename x y) fields), q)+rename x y (Comp z src body, q) + | x == z = (Comp z src body, q)+ | y == z = let y' = head $ variables \\ [y] in+ let body' = rename y y' body in+ (Comp z (rename x y src) (rename x y body'), q)+ | otherwise= (Comp z (rename x y src) (rename x y body), q)+rename x y (String n, q) = (String n, q)+rename x y (Bool b, q) = (Bool b, q)+rename x y (Table s t, q) = (Table s t, q)+rename x y (If c a b, q) = (If (rename x y c) (rename x y a) (rename x y b), q)+rename x y (Unit, q) = (Unit, q)+rename x y (Nil, q) = (Nil, q)+rename x y (Union a b, q) = (Union (rename x y a) (rename x y b), q)++-- | substTerm x v m: substite v for x in term m+-- (Actually incorrect because it does not make substitutions in the q.)+substTerm :: Var -> Term t -> Term t -> Term t+substTerm x v (m@(Unit, _)) = m+substTerm x v (m@(Bool b, _)) = m+substTerm x v (m@(Num n, _)) = m+substTerm x v (m@(String s, _)) = m+substTerm x v (m@(Table s t, _)) = m+substTerm x v (m@(Nil, _)) = m+substTerm x v (Singleton elem, q) = (Singleton (substTerm x v elem), q)+substTerm x v (Union m n, q) = (Union (substTerm x v m) (substTerm x v n), q)+substTerm x v (m@(Var y, _)) | y == x = v+ | otherwise = m+substTerm x v (l @ (Abs y n, q))+ | x == y = l+ | y `notElem` fvs v = (Abs y (substTerm x v n), q) + | otherwise = + let y' = head $ variables \\ fvs v in+ let n' = rename y y' n in+ (Abs y' (substTerm x v n'), q)+substTerm x v (App l m, q) = (App (substTerm x v l) (substTerm x v m), q)+substTerm x v (PrimApp prim args,q)= (PrimApp prim (map (substTerm x v) args),q)+substTerm x v (Project targ label, q) = (Project (substTerm x v targ) label, q)+substTerm x v (Record fields, q) = (Record (alistmap (substTerm x v) fields), q)+substTerm x v (Comp y src body, q) + | x == y =+ (Comp y src' body, q)+ | y `notElem` fvs v =+ (Comp y src' (substTerm x v body), q)+ | otherwise = + let y' = head $ variables \\ fvs v in+ let body' = rename y y' body in+ (Comp y' src' (substTerm x v body'), q)+ where src' = (substTerm x v src)+substTerm x v (If c a b, q) = + (If (substTerm x v c) (substTerm x v a) (substTerm x v b), q)++-- | lazyDepth: calculate a list (poss. inf.) whose sum is the depth+-- of the term. (unused)+lazyDepth :: Term a -> [Int]+lazyDepth (Abs _ n, _) = 1 : lazyDepth n+lazyDepth (App l m, _) = 1 : zipWith max (lazyDepth l) (lazyDepth m)+lazyDepth (Project m _, _) = 1 : lazyDepth m+lazyDepth (Singleton m, _) = 1 : lazyDepth m+lazyDepth (PrimApp prim args, _) =+ 1 : foldr1 (zipWith max) (map lazyDepth args)+lazyDepth (Record fields, _) =+ 1 : foldr1 (zipWith max) (map (lazyDepth . snd) fields)+lazyDepth (Comp _ src body, _) =+ 1 : zipWith max (lazyDepth src) (lazyDepth body)+lazyDepth _ = 1 : []++-- Generic term-recursion functions ------------------------------------++entagulate :: (Term a -> b) -> Term a -> Term b+entagulate f (Bool b, d) = (Bool b, f (Bool b, d))+entagulate f (Num n, d) = (Num n, f (Num n, d))+entagulate f (String s, d) = (String s, f (String s, d))+entagulate f (Var x, d) = (Var x, f (Var x, d))+entagulate f (Abs x n, d) = (Abs x (entagulate f n), f (Abs x n, d))+entagulate f (App l m, d) = (App (entagulate f l) (entagulate f m),+ f (App l m, d))+entagulate f (If c a b, d) =+ (If (entagulate f c)+ (entagulate f a)+ (entagulate f b),+ f (If c a b, d))+entagulate f (Table tab fields, d) = (Table tab fields, f (Table tab fields, d))+entagulate f (Nil, d) = (Nil, f (Nil,d))+entagulate f (Singleton m, d) = (Singleton (entagulate f m),+ f (Singleton m, d))+entagulate f (Union a b, d) =+ (Union+ (entagulate f a)+ (entagulate f b),+ f (Union a b, d))+entagulate f (Record fields, d) = (Record (alistmap (entagulate f) fields), + f (Record fields, d))+entagulate f (Project m a, d) = (Project (entagulate f m) a,+ f (Project m a, d))+entagulate f (Comp x src body, d) = + (Comp x (entagulate f src) (entagulate f body),+ f (Comp x src body, d))++retagulate :: (Term a -> a) -> Term a -> Term a+retagulate f (Unit, d) = (Unit, f (Unit, d))+retagulate f (Bool b, d) = (Bool b, f (Bool b, d))+retagulate f (Num n, d) = (Num n, f (Num n, d))+retagulate f (String s, d) = (String s, f (String s, d))+retagulate f (Var x, d) = (Var x, f (Var x, d))+retagulate f (Abs x n, d) = (Abs x (retagulate f n),+ f (Abs x (retagulate f n), d))+retagulate f (App l m, d) = (App (retagulate f l) (retagulate f m),+ f (App (retagulate f l) (retagulate f m), d))+retagulate f (PrimApp fn ar, d) = (PrimApp fn (map (retagulate f) ar),+ f (PrimApp fn (map (retagulate f) ar), d))+retagulate f (If c a b, d) =+ (If (retagulate f c)+ (retagulate f a)+ (retagulate f b),+ f (If (retagulate f c)+ (retagulate f a)+ (retagulate f b), d))+retagulate f (Table tab fields, d) = (Table tab fields, f (Table tab fields, d))+retagulate f (Nil, d) = (Nil, f (Nil, d))+retagulate f (Singleton m, d) = (Singleton (retagulate f m),+ f (Singleton (retagulate f m), d))+retagulate f (Union l m, d) = (Union (retagulate f l) (retagulate f m),+ f (Union (retagulate f l) (retagulate f m), d))+retagulate f (Record fields, d) = (Record (alistmap (retagulate f) fields), + f (Record (alistmap (retagulate f) fields), d))+retagulate f (Project m a, d) = (Project (retagulate f m) a,+ f (Project (retagulate f m) a, d))+retagulate f (Comp x src body, d) = + (Comp x (retagulate f src) (retagulate f body),+ f (Comp x (retagulate f src) (retagulate f body), d))++strip = entagulate (const ())++-- | numComps: Number of comprehensions in an expression, a measure of+-- the complexity of the query.+numComps (Comp x src body, _) = 1 + numComps src + numComps body+numComps (PrimApp _ args, _) = sum $ map numComps args+numComps (Abs _ n, _) = numComps n+numComps (App l m, _) = numComps l + numComps m+numComps (Singleton body, _) = numComps body+numComps (Record fields, _) = sum $ map (numComps . snd) fields+numComps (Project m _, _) = numComps m+numComps (Union a b, _) = numComps a + numComps b+numComps (Unit, _) = 0+numComps (Bool _, _) = 0+numComps (Num _, _) = 0+numComps (String _, _) = 0+numComps (Var _, _) = 0+numComps (Table _ _, _) = 0+numComps (If c a b, _) = numComps c + numComps a + numComps b+numComps (Nil, _) = 0++-- | An interface for semanticizing the Narc concrete language as+-- | desired (as per "Unembedding domain specific languages" by Atkey,+-- | Lindley and Yallop).+class NarcSem result where+ unit :: result+ bool :: Bool -> result+ num :: Integer -> result+ string :: String -> result+ primApp :: String -> [result] -> result+ var :: Var -> result+ abs :: Var -> result -> result+ app :: result -> result -> result+ table :: Tabname -> [(Field, Type)] -> result+ ifthenelse :: result -> result -> result -> result+ singleton :: result -> result+ nil :: result+ union :: result -> result -> result+ record :: [(String, result)] -> result+ project :: result -> String -> result+ foreach :: result -> Var -> result -> result+-- cnst :: Constable t => t -> result+class Constable t where cnst :: NarcSem result => t -> result+instance Constable Bool where cnst b = bool b+instance Constable Integer where cnst n = num n++-- Explicit-named builders++(!) x = (x, ())++instance NarcSem (Term'(),()) where+ unit = (!)Unit+ bool b = (!)(Bool b)+ num n = (!)(Num n)+ string n = (!)(String n)+ primApp f args = (!)(PrimApp f args)+ var x = (!)(Var x)+ abs x body = (!)(Abs x body)+ app l m = (!)(App l m)+ table tbl ty = (!)(Table tbl ty)+ ifthenelse c t f = (!)(If c t f)+ singleton x = (!)(Singleton x)+ nil = (!)Nil+ union a b = (!)(Union a b)+ record fields = (!)(Record fields)+ project body field = (!)(Project body field)+ foreach src x body = (!)(Comp x src body)+-- class Const a where cnst_ :: a -> Term ()++unit_ = (!)Unit+class Const a where cnst_ :: a -> Term ()+instance Const Bool where cnst_ b = (!)(Bool b)+instance Const Integer where cnst_ n = (!)(Num n)+primApp_ f args = (!)(PrimApp f args)+var_ x = (!)(Var x)+abs_ x body = (!)(Abs x body)+app_ l m = (!)(App l m)+table_ tbl ty = (!)(Table tbl ty)+ifthenelse_ c t f = (!)(If c t f)+singleton_ x = (!)(Singleton x)+nil_ = (!)Nil+union_ a b = (!)(Union a b)+record_ fields = (!)(Record fields)+project_ body field = (!)(Project body field)+foreach_ src x body = (!)(Comp x src body)
+ Database/Narc/AST/Pretty.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE TypeSynonymInstances #-}++module Database.Narc.AST.Pretty where++import Database.Narc.AST+import Database.Narc.Pretty+import Database.Narc.Util (mapstrcat)++-- Pretty-printing ------------------------------------------------=====++instance Pretty (Term' a) where+ pretty (Unit) = "()"+ pretty (Bool b) = show b+ pretty (Num n) = show n+ pretty (PrimApp f args) = f ++ "(" ++ mapstrcat "," pretty args ++ ")"+ pretty (Var x) = x+ pretty (Abs x n) = "(fun " ++ x ++ " -> " ++ pretty n ++ ")"+ pretty (App l m) = pretty l ++ " " ++ pretty m+ pretty (Table tbl t) = "(table " ++ tbl ++ " : " ++ show t ++ ")"+ pretty (If c a b) =+ "(if " ++ pretty c ++ " then " ++ pretty a ++ + " else " ++ pretty b ++ " )"+ pretty (Singleton m) = "[" ++ pretty m ++ "]" + pretty (Nil) = "[]"+ pretty (Union m n) = "(" ++ pretty n ++ " ++ " ++ pretty n ++ ")"+ pretty (Record fields) = + "{" ++ mapstrcat "," (\(l,m) -> l ++ "=" ++ pretty m) fields ++ "}"+ pretty (Project m l) = "(" ++ pretty m ++ "." ++ l ++ ")"+ pretty (Comp x m n) =+ "(for (" ++ x ++ " <- " ++ pretty m ++ ") " ++ pretty n ++ ")"++instance Pretty (Term a) where+ pretty (m, t) = pretty m
+ Database/Narc/Common.hs view
@@ -0,0 +1,6 @@+module Database.Narc.Common where++type Tabname = String++type Field = String+
+ Database/Narc/Compile.hs view
@@ -0,0 +1,226 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Database.Narc.Compile (compile) where++import Data.List ((\\))++import Database.Narc.AST+import Database.Narc.AST.Pretty ()+import Database.Narc.Contract+import Database.Narc.Debug (forceAndReport)+import Database.Narc.Pretty+import Database.Narc.SQL as SQL+import Database.Narc.Type as Type+import Database.Narc.TypeInfer+import Database.Narc.Util (image, maps, alistmap)++-- -- Testing-related imports+-- import Test.QuickCheck (Property, forAll, sized)+-- import Database.Narc.TermGen+-- import Database.Narc.Eval+-- import Database.Narc.Failure++-- { Compilation } -----------------------------------------------------++etaExpand :: TypedTerm -> [(String, Type)] -> TypedTerm+etaExpand expr fieldTys =+ let exprTy = TRecord fieldTys in+ (Record [(field, ((Project expr field), fTy))+ | (field, fTy) <- fieldTys], + exprTy)++-- | Normalize DB terms in a nearly call-by-value way.+normTerm :: [(String, QType)] -- ^ An environment, typing all free vars.+ -> TypedTerm -- ^ The term to normalize.+ -> TypedTerm+normTerm _env (m@(Unit, _ty)) = m+normTerm _env (m@(Bool _, _)) = m+normTerm _env (m@(Num _, _)) = m+normTerm _env (m@(String _, _)) = m+normTerm env (PrimApp fun args, t) = (PrimApp fun (map (normTerm env) args), t)+normTerm env (expr@(Var x, t)) = + -- Eta-expand at record type.+ if (maps x) env then + case t of+ TRecord t' -> etaExpand expr t'+ _ -> (Var x, t) + else+ error $ "Free variable "++ x ++ " in normTerm"+normTerm _env (Abs x n, t) =+ (Abs x n, t)+normTerm env (App l m, t) = + let w = normTerm env m in+ case normTerm env l of + (Abs x n, _) -> + forceAndReport (+ let !n' = substTerm x w n in+ normTerm env (runTyCheck env $ n')+ ) ("susbtituting "++show w++" for "++x++" in "++show n)+ (If b l1 l2, _) ->+ (normTerm env (If b (App l1 w, t) (App l2 w, t), t))+ v@(Var _, _) -> (App v w, t)+ v -> error $ "unexpected normal form in appl posn in normTerm " ++ show v+normTerm _env (Table s t, t') = (Table s t, t')+normTerm env (If b m (Nil, _), t@(TList _)) =+ let b' = normTerm env b in+ case normTerm env m of+ (Nil, _) -> (Nil, t)+ (Singleton m', _) -> (If b' (Singleton m', t) (Nil, t), t)+ (Table s fTys, _) -> (If b' (Table s fTys, t) (Nil, t), t)+ (Comp x l m', _) -> normTerm env (Comp x l (If b' m' (Nil, t), t), t)+ (m1 `Union` m2, _) -> ((normTerm env (If b' m1 (Nil, t), t)) `Union`+ (normTerm env (If b' m2 (Nil, t), t)), t)+ v@(If _ _ _, _) -> (If b' v (Nil, t), t)+ v -> error $ "Unexpected normal form in conditional body in normTerm: " +++ show v+normTerm env (If b@(_,bTy) m n, t@(TList _)) = -- The case where n /= Nil+ ((normTerm env (If b m (Nil, t), t)) `Union` + (normTerm env (If (PrimApp "not" [b], bTy) n (Nil, t), t)), t)+normTerm env (If b m n, t@(TRecord fTys)) =+ let b' = normTerm env b in+ let (Record mFields, _) = normTerm env m+ (Record nFields, _) = normTerm env n in+ (Record [(l, (If b' (image l mFields) (image l nFields), (image l fTys)))+ | (l, _) <- mFields],+ t)+normTerm env (If b m n, t) = + (If (normTerm env b) (normTerm env m) (normTerm env n), t)+normTerm env (Singleton m, t) = (Singleton (normTerm env m), t)+normTerm _env (Nil, t) = (Nil, t)+normTerm env (m `Union` n, t) = ((normTerm env m) `Union` (normTerm env n), t)+normTerm env (Record fields, t) =+ (Record [(a, normTerm env m) | (a, m) <- fields], t)+normTerm env (Project argTerm label, t) = + case normTerm env argTerm of+ (Record fields, _) -> case (lookup label fields) of + Just x -> x + Nothing -> error $ "no field " ++ label+ -- Ah, the following not necessary because If pushes into records.+ (If condn v1 v2,_) ->+ normTerm env (If condn+ (Project v1 label, t)+ (Project v2 label, t), t)+ v@(Var _x, _) -> (Project v label, t)+ v -> error $ "Unexpected normal form in body of Project in normTerm: " ++ + show v+normTerm env (Comp x src body, t) =+ case normTerm env src of+ (Nil, _) -> (Nil, t)+ (Singleton src', _) -> + forceAndReport (+ let !n' = substTerm x src' body in+ normTerm env (runTyCheck env n')+ ) ("Substituting " ++ show src' ++ " for " ++ x ++ " in " ++ show body)+ (Comp y src2 body2, _) ->+ -- Freshen @y@ over @src@ with respect to @body@ (that of+ -- the outer comprehension), because we're widening the+ -- scope of @y@ to include @body@.+ let (y', body') = if y `elem` fvs body then+ let newY = minFreeFor body in+ (newY, rename y newY body)+ else (y, body)+ in+ (normTerm env (Comp y' src2 (Comp x body2 body', t), t))+ (srcL `Union` srcR, _) ->+ ((normTerm env (Comp x srcL body, t)) `Union` + (normTerm env (Comp x srcR body, t)), t)+ (tbl @ (Table _tableName fieldTys, _)) ->+ insert (\(v',t') -> (Comp x tbl (v',t'), t')) $+ let env' = Type.bind x ([],TList(TRecord fieldTys)) env in + normTerm env' body+ (If cond' src' (Nil, _), _) ->+ assert (x `notElem` fvs cond') $+ let v = normTerm env (Comp x src' body, t) in+ insertFurther (\(v',t') -> (If cond' (v',t') (Nil, t'), t')) v+ v -> error $+ "unexpected normal form in source part of comprehension: " +++ show v++-- Insertion functions for rebuilding a term, dropping a+-- reconstructor k down through unions and compr'ns (there must be+-- a better way!).+insert :: (TypedTerm -> TypedTerm) -> TypedTerm -> TypedTerm+insert k ((v,t) :: TypedTerm) =+ case v of+ Nil -> (Nil, t)+ n1 `Union` n2 -> ((insert k n1) `Union` (insert k n2), t)+ _ -> k (v,t)++insertFurther :: (TypedTerm -> TypedTerm) -> TypedTerm -> TypedTerm+insertFurther k ((v,t) :: TypedTerm) =+ case v of+ Nil -> (Nil, t)+ n1 `Union` n2 -> + ((insertFurther k n1) `Union` (insertFurther k n2), t)+ Comp x m n -> (Comp x m (insertFurther k n), t)+ _ -> k (v,t)++-- See (Bird 2010) for a better algorithm here.+minFreeFor :: Term a -> Var+minFreeFor n = head $ variables \\ fvs n ++-- | @translateTerm@ homomorphically translates a normal-form Term to an+-- | SQL Query.+translateTerm :: TypedTerm -> Query+translateTerm (v `Union` u, _) = (translateTerm v) `QUnion` (translateTerm u)+translateTerm (Nil, _) = SQL.emptyQuery+translateTerm (f@(Comp _ (Table _ _, _) _, _)) = translateF f+translateTerm (f@(If _ _ (Nil, _), _)) = translateF f+translateTerm (f@(Singleton (Record _, _), _)) = translateF f+translateTerm (f@(Table _ _, _)) = translateF f+translateTerm x = + error $ "translateTerm got unexpected term: " ++ (pretty.fst) x++-- translateF, translateZ and translateB are named after the syntactic+-- classes (in the grammar of the normalized form) which they handle.+-- (F for "for comprehension", Z for "final bit of a nest of+-- comprehensions", and B for "base type"+translateF :: Term b -> Query+translateF (Comp x (Table tabname fTys, _) n, _) =+ let q@(Select _ _ _) = translateF n in+ Select {rslt = rslt q,+ tabs = (tabname, x, TRecord fTys):tabs q,+ cond = cond q}+translateF (z@(If _ _ (Nil, _), _)) = translateZ z+translateF (z@(Singleton (Record _, _), _)) = translateZ z+translateF (z@(Table _ _, _)) = translateZ z+translateF m = error $ "translateF for unexpected term: " ++ pretty (fst m)++translateZ :: Term b -> Query+translateZ (If b z (Nil, _), _) =+ let q@(Select _ _ _) = translateZ z in+ Select {rslt=rslt q, tabs = tabs q, cond = translateB b : cond q}+translateZ (Singleton (Record fields, _), _) = + Select {rslt = QRecord(alistmap translateB fields), tabs = [], cond = []}+translateZ (Table tabname fTys, _) =+ Select {rslt = QRecord[(l,QField tabname l)| (l,_ty) <- fTys],+ tabs = [(tabname, tabname, TRecord fTys)], cond = []}+translateZ z = error$ "translateZ got unexpected term: " ++ (pretty.fst) z++translateB :: Term b -> Query+translateB (If b b' b'', _) = QIf (translateB b)+ (translateB b') (translateB b'') +translateB (Bool n, _) = (QBool n)+translateB (Num n, _) = (QNum n)+translateB (Project (Var x, _) l, _) = QField x l+translateB (PrimApp "not" [arg], _) = QNot (translateB arg)+translateB (PrimApp "<" [l, r], _) = QOp (translateB l) Less (translateB r)+translateB b = error$ "translateB got unexpected term: " ++ (pretty.fst) b++compile :: TyEnv -> TypedTerm -> Query+compile env = translateTerm . normTerm env++-- -- Tests++-- -- FIXME: where does this belong? It tests a function internal to this+-- -- module (normTerm) but uses testing apparatus that is defined at a+-- -- "higher" layer (Database.Narc.Test) and uses an otherwise unrelated module+-- -- (Database.Narc.Eval).+-- prop_norm_sound :: TyEnv -> Env -> Property+-- prop_norm_sound tyEnv env =+-- forAll (sized (typeGen [])) $ \t ->+-- forAll (sized (typedTermGen tyEnv t)) $ \m ->+-- isErrorMSuccess $ tryErrorGensym $ +-- do m' <- infer m+-- return (eval env (normTerm tyEnv m') == eval env m')
+ Database/Narc/Contract.hs view
@@ -0,0 +1,7 @@+module Database.Narc.Contract where++-- Contractual assertions ----------------------------------------------++contract p x = if p x then x else error "Contract broken"++assert x e = if x then e else error "assertion failed"
+ Database/Narc/Debug.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Database.Narc.Debug where++import Prelude hiding (catch)+import Control.Exception (catch, evaluate, throwIO, SomeException)+import Debug.Trace (trace)+import Foreign (unsafePerformIO)++-- | Enable/disable debugging messages+debugFlag :: Bool+debugFlag = False++-- | Trace the given string if debugging is on, or do nothing if not.+debug :: String -> a -> a+debug str = if debugFlag then trace str else id++breakFlag x = x -- a hook for a breakpoint in GHCi debugger++-- | Force an arbitrary expression, tracing the @String@ arg if+-- forcing produces an exception.+forceAndReport :: a -> String -> a+forceAndReport expr msg = + unsafePerformIO $+ catch (evaluate $+ expr `seq` expr+ ) (\(exc::SomeException) ->+ breakFlag $ + debug msg $ + Control.Exception.throwIO exc+ )
+ Database/Narc/Eval.hs view
@@ -0,0 +1,119 @@+module Database.Narc.Eval where++import Database.Narc.AST+import Database.Narc.Debug (debug)+import Database.Narc.Util (alistmap)++--+-- Evaluation ----------------------------------------------------------+--++-- { Values and value environments } -----------------------------------++bind x v env = (x,v):env++-- type RuntimeTerm = Term (Maybe Query)++type Env = [(Var, Value)]++data Value = VUnit | VBool Bool | VNum Integer+ | VList [Value]+ | VRecord [(String, Value)]+ | VAbs Var TypedTerm Env+ deriving (Eq, Show)++fromValue :: Value -> TypedTerm+fromValue VUnit = (Unit, undefined)+fromValue (VBool b) = (Bool b, undefined)+fromValue (VNum n) = (Num n, undefined)+fromValue (VList xs) = foldr1 union (map singleton $ map fromValue xs)+ where union x y = (x `Union` y, undefined)+ singleton x = (Singleton x, undefined)+fromValue (VRecord fields) = (Record (alistmap fromValue fields), undefined)+fromValue (VAbs x n env) = foldr (\(y,v) -> substTerm y (fromValue v))+ (Abs x n, undefined) env++concatVLists xs = VList $ concat [x | (VList x)<-xs]++initialEnv :: Env+initialEnv =+ []+-- [("+",+-- ((VAbs "x" (Abs "y"+-- (PrimApp "+" [(Var "x", (TNum, openEpe), (Var "y", TNum)],+-- Just (QOp (QVar "x") Plus (QVar "y"))), TNum) []),+-- Just (QAbs "x" (QAbs "y" (QOp (QVar "x") Plus (QVar "y"))))))]++-- | appPrim: apply a primitive function to a list of value arguments.+appPrim :: String -> [Value] -> Value+appPrim "+" [VNum a, VNum b] = VNum (a+b)+appPrim p _ = error("Unknown primitive" ++ p)++-- | eval: Evaluate a typed term in a closing environment. Captures the+-- effects performed by the term. (NB: type info is not actually used;+-- should eliminate this.)+eval :: Env -> TypedTerm -> Value+eval env (Unit, _) = (VUnit)+eval env (Bool b, q) = (VBool b)+eval env (Num n, q) = (VNum n)+eval env (PrimApp prim args, q) = + let (vArgs) = map (eval env) args in+ (appPrim prim vArgs)+eval env (Var x, q) =+ case lookup x env of+ Nothing -> error+ ("Variable " ++ x ++ " not found in env " ++ show env ++ + " while evaluating term.")+ Just v -> v+eval env (Abs x n, q) = (VAbs x n env')+ where env' = filter (\(a,b) -> a `elem` fvs n) env+eval env (App l m, q) = + let (v) = eval env l in+ let (w) = eval env m in+ case v of+ (VAbs x n env') -> + let env'' = bind x w env' in+ let (r) = eval env'' n in+ (r)+ _ -> error "non-function applied"+eval env (Table name fields, q) = + (VList [])+eval env (If c a b, _) =+ let (VBool t) = eval env c in+ let (result) = if t then eval env a else eval env b in+ (result)+eval env (Nil, _) =+ (VList [])+eval env (Singleton body, q) =+ let (v) = eval env body in+ (VList [v])+eval env (Union m n, _) =+ let (VList v) = eval env m in+ let (VList w) = eval env n in+ (VList $ v ++ w)+eval env (Record fields, q) =+ let (vFields) = [let (value) = eval env term in+ ((name, value))+ | (name, term) <- fields] in+ (VRecord vFields)+eval env (Project m f, q) =+ let (v) = eval env m in+ case v of+ VRecord fields -> + case lookup f fields of {+ Nothing -> error $ "No field " ++ f ++ " in " ++ + show v ++ "(" ++ show m ++ ")" ;+ Just vField -> vField+ }+ _ -> error("Non-record value " ++ show v ++ " target of projection " ++ + show(Project m f))+eval env (Comp x src body, q) =+ let (vSrc) = eval env src in+ case vSrc of+ (VList elems) -> + let (results) = [eval (bind x v env) body+ | v <- elems] in+ (concatVLists results)+ _ -> error("Comprehension source was not a list.")++run = eval initialEnv
+ Database/Narc/Failure.hs view
@@ -0,0 +1,46 @@+module Database.Narc.Failure where++import Control.Monad.Error hiding (when, join)+import Gensym+import Database.Narc.Debug++-- Failure and ErrorGensym monads --------------------------------------+-- (TBD: this is more general than Narc; factor it out.)++type Failure a = Either String a++fayl = Left -- TBD: would be better to make Failure a newtype & use+ -- fail from Monad.++-- instance Monad Failure where+-- return = Failure . Right+-- fail = Failure . Left+-- x >>= k = case x of Failure(Left err) -> Failure(Left err)+-- Failure(Right x') -> k x'++runError :: Either String t -> t+runError (Left e) = breakFlag $ error e+runError (Right x) = x++isError (Left x) = True+isError (Right _) = False++isSuccess (Left _) = False+isSuccess (Right _) = True++type ErrorGensym a = ErrorT String Gensym a++-- | Run an ErrorGensym action, raising errors with `error'.+runErrorGensym = runError . runGensym . runErrorT++-- | Try running an ErrorGensym action, packaging result in an Either+-- | with Left as failure, Right as success.+tryErrorGensym = runGensym . runErrorT++under x = either throwError return x++isErrorMSuccess = either (const False) (const True) ++instance Error () where+ noMsg = ()+ strMsg _ = ()
+ Database/Narc/Failure/QuickCheck.hs view
@@ -0,0 +1,17 @@+module Database.Narc.Failure.QuickCheck where++import Test.QuickCheck++import QCUtils+import Database.Narc.Failure++-- QuickCheck property utilities ---------------------------------------++failureToProperty :: Test.QuickCheck.Testable a => Failure a -> Property+failureToProperty (Left _) = failProp+failureToProperty (Right x) = property x++failureToPropertyIgnoreFailure :: Test.QuickCheck.Testable a => + Failure a -> Property+failureToPropertyIgnoreFailure (Left _) = ignore+failureToPropertyIgnoreFailure (Right x) = property x
+ Database/Narc/HDBC.hs view
@@ -0,0 +1,17 @@+module Database.Narc.HDBC where++import Database.HDBC++import Database.Narc.AST+import Database.Narc.SQL+import Database.Narc.Compile+import Database.Narc.TypeInfer++-- | Run a Narc query directly against an HDBC connection.+run :: IConnection conn => Term a -> conn -> IO [[SqlValue]]+run t conn =+ let sql = serialize (compile [] (runTyCheck [] t)) in+ quickQuery conn sql []++-- FIXME: We need a version of the above that takes a NarcTerm instead+-- of a concrete term.
+ Database/Narc/Pretty.hs view
@@ -0,0 +1,8 @@+module Database.Narc.Pretty where++import Database.Narc.Common++-- Pretty-printing ------------------------------------------------=====++class Pretty t where+ pretty :: t -> String
+ Database/Narc/Rewrite.hs view
@@ -0,0 +1,67 @@+module Database.Narc.Rewrite where++import Data.Maybe (fromMaybe)++import Database.Narc.AST+import Database.Narc.Type+import Database.Narc.Util (alistmap)++-- Rewrite -------------------------------------------------------------+--+-- Small-step version of compilation: local rewrite rules applied+-- willy-nilly.++perhaps :: (a -> Maybe a) -> a -> a+perhaps f x = fromMaybe x (f x)++bu :: (Term a -> Maybe (Term a)) -> Term a -> Term a+bu f (Unit, d) = perhaps f (Unit, d)+bu f (Bool b, d) = perhaps f (Bool b, d)+bu f (Num n, d) = perhaps f (Num n, d)+bu f (Var x, d) = perhaps f (Var x, d)+bu f (Abs x n, d) = perhaps f (Abs x (bu f n), d)+bu f (App l m, d) = perhaps f (App (bu f l) (bu f m), d)+bu f (If c a b, d) =+ perhaps f (If (bu f c)+ (bu f a)+ (bu f b), d)+bu f (Table tab fields, d) = perhaps f (Table tab fields, d)+bu f (Singleton m, d) = perhaps f (Singleton (bu f m), d)+bu f (Record fields, d) = perhaps f (Record (alistmap (bu f) fields), d)+bu f (Project m a, d) = perhaps f (Project (bu f m) a, d)+bu f (Comp x src body, d) = perhaps f (Comp x (bu f src) (bu f body), d)+bu f (PrimApp fun args, d) = perhaps f (PrimApp fun args, d)+bu f (Nil, d) = perhaps f (Nil, d)+bu f (Union a b, d) = perhaps f (Union a b, d)++rw (Comp x (Singleton m, _) n, t) = Just (substTerm x m n)+rw (App (Abs x n, st) m, t) = Just (substTerm x m n)+rw (Project (Record fields, rect) fld, t) = lookup fld fields+rw (Singleton (Var x, xT), t) = Nothing -- for now+rw (Comp x (Nil, _) n, t) = Just (Nil, t)+rw (Comp x m (Nil, _), t) = Just (Nil, t)+rw (Comp x (Comp y l m, s) n, t) = + if y `notElem` fvs n then+ Just (Comp y l (Comp x m n, t), t)+ else Nothing+rw (Comp x (m1 `Union` m2, s) n, t) =+ Just ((Comp x m1 n, t) `Union` (Comp x m2 n, t), t)+rw (Comp x m (n1 `Union` n2, _), t) =+ Just ((Comp x m n1, t) `Union` (Comp x m n2, t), t)+rw (Comp x (If b m (Nil, _), _) n, t) =+ Just (Comp x m (If b n (Nil, t), t), t)+rw (If (b, bTy) m n, t@(TList _, _)) | fst n /= Nil =+ Just((If (b,bTy) m (Nil, t), t) `Union`+ (If (PrimApp "not" [(b, bTy)], bTy) n (Nil, t), t), t)+rw (If b (Nil, _) (Nil, _), t) = Just (Nil, t)+rw (If b (Comp x m n, _) (Nil, _), t) = Just (Comp x m (If b n (Nil, t), t), t) +rw (If b (m1 `Union` m2, _) (Nil, _), t) =+ Just ((If b m1 (Nil, t), t) `Union` (If b m2 (Nil, t), t), t)+-- push App inside If+-- push Project inside If+-- push If inside Record+-- rw (IsEmpty m, t) +-- | lorob t = Nothing+-- | otherwise = +-- IsEmpty (Comp "x" m (Singleton (Unit, TUnit), TList Tunit), TList TUnit)+rw _ = Nothing
+ Database/Narc/SQL.hs view
@@ -0,0 +1,155 @@+module Database.Narc.SQL where++import Data.List (nub, intercalate)++import Database.Narc.Common+import Database.Narc.Type+import Database.Narc.Util (u, mapstrcat)++--+-- SQL Queries ---------------------------------------------------------+--++data Op = Eq | Less+ | Plus | Minus | Times | Divide+ deriving(Eq, Show)++data UnOp = Min | Max | Count | Sum | Average+ deriving (Eq, Show)++-- | Query: the type of SQL queries ("select R from Ts where B")+-- (This is unpleasant; it should probably be organized into various+-- syntactic classes.)+data Query = Select {rslt :: Query, -- make this a list+ tabs :: [(Field, Field, Type)], -- use [(Field,Type)]+ cond :: [Query]+ }+ | QNum Integer+ | QBool Bool+ | QNot Query+ | QOp Query Op Query+ | QField String String+ | QRecord [(Field, Query)]+ | QUnion Query Query+ | QIf Query Query Query+ | QExists Query+ deriving(Eq, Show)++emptyQuery = Select {rslt = QRecord [], tabs = [], cond = [QBool False]}++-- | @sizeQuery@ approximates the size of a query by calling giving up+-- | its node count past a certain limit (currently limit = 100, below).+sizeQueryExact :: Query -> Integer+sizeQueryExact (q@(Select _ _ _)) =+ sizeQueryExact (rslt q) + (sum $ map sizeQueryExact (cond q))+sizeQueryExact (QNum n) = 1+sizeQueryExact (QBool b) = 1+sizeQueryExact (QNot q) = 1 + sizeQueryExact q+sizeQueryExact (QOp a op b) = 1 + sizeQueryExact a + sizeQueryExact b+sizeQueryExact (QField t f) = 1+sizeQueryExact (QRecord fields) = sum [sizeQueryExact n | (a, n) <- fields]+sizeQueryExact (QUnion m n) = sizeQueryExact m + sizeQueryExact n+sizeQueryExact (QIf c a b) = sizeQueryExact c + sizeQueryExact a + sizeQueryExact b+sizeQueryExact (QExists q) = 1 + sizeQueryExact q++-- | @sizeQuery@ approximates the size of a query by calling giving up+-- | its node count past a certain limit (currently limit = 100, below).+sizeQuery :: Query -> Integer+sizeQuery qy = loop 0 qy+ where+ loop' :: Integer -> Query -> Integer+ loop' n qy = if n > limit then n else loop n qy++ loop :: Integer -> Query -> Integer+ loop n (q@(Select _ _ _)) = + let n' = foldr (\r n -> loop' n r) n (cond q) in+ loop' n' (rslt q)+ loop n (QNum i) = n + 1+ loop n (QBool b) = n + 1+ loop n (QNot q) = loop' (n+1) q+ loop n (QOp a op b) = let n' = loop' (n+1) a in loop' n' b+ loop n (QField t f) = n + 1+ loop n (QRecord fields) = foldr (\r n -> loop' n r) n (map snd fields)+ loop n (QUnion a b) = let n' = loop' (n+1) a in loop' n' b+ loop n (QIf c a b) = + let n' = loop' (n+1) c in+ let n'' = loop' n' a in+ loop' n'' b+ loop n (QExists q) = loop' (n+1) q++ limit = 100++-- Basic functions on query expressions --------------------------------++freevarsQuery (q@(Select _ _ _)) = + (freevarsQuery (rslt q))+ `u`+ (nub $ concat $ map freevarsQuery (cond q))+freevarsQuery (QOp lhs op rhs) = nub (freevarsQuery lhs ++ freevarsQuery rhs)+freevarsQuery (QRecord fields) = concatMap (freevarsQuery . snd) fields+freevarsQuery _ = []++isQRecord (QRecord _) = True+isQRecord _ = False++-- | a groundQuery is a *real* SQL query--one without variables or appl'ns.+groundQuery :: Query -> Bool+groundQuery (qry@(Select _ _ _)) =+ all groundQueryExpr (cond qry) &&+ groundQueryExpr (rslt qry) &&+ isQRecord (rslt qry)+groundQuery (QUnion a b) = groundQuery a && groundQuery b+groundQuery (QExists qry) = groundQuery qry+groundQuery (QRecord fields) = all (groundQuery . snd) fields+groundQuery (QOp b1 _ b2) = groundQuery b1 && groundQuery b2+groundQuery (QNum _) = True+groundQuery (QBool _) = True+groundQuery (QField _ _) = True+groundQuery (QNot a) = groundQuery a++-- | a groundQueryExpr is an atomic-type expression.+groundQueryExpr :: Query -> Bool+groundQueryExpr (qry@(Select _ _ _)) = False+groundQueryExpr (QUnion a b) = False+groundQueryExpr (QExists qry) = groundQuery qry+groundQueryExpr (QRecord fields) = all (groundQueryExpr . snd) fields+groundQueryExpr (QOp b1 _ b2) = groundQueryExpr b1 && groundQueryExpr b2+groundQueryExpr (QNot a) = groundQueryExpr a+groundQueryExpr (QNum _) = True+groundQueryExpr (QBool _) = True+groundQueryExpr (QField _ _) = True+groundQueryExpr (QIf c a b) = all groundQueryExpr [c,a,b]++serialize :: Query -> String+serialize q@(Select _ _ _) =+ "select " ++ serializeRow (rslt q) +++ " from " ++ mapstrcat ", " (\(a, b, _) -> a ++ " as " ++ b) (tabs q) +++ " where " ++ if null (cond q) then+ "true"+ else mapstrcat " and " serializeAtom (cond q)+serialize (QUnion l r) =+ "(" ++ serialize l ++ ") union (" ++ serialize r ++ ")"++serializeRow (QRecord flds) =+ mapstrcat ", " (\(x, expr) -> serializeAtom expr ++ " as " ++ x) flds++serializeAtom (QNum i) = show i+serializeAtom (QBool b) = show b+serializeAtom (QNot expr) = "not(" ++ serializeAtom expr ++ ")"+serializeAtom (QOp l op r) = + serializeAtom l ++ " " ++ serializeOp op ++ " " ++ serializeAtom r+serializeAtom (QField rec fld) = rec ++ "." ++ fld+serializeAtom (QIf cond l r) = + "case when " ++ serializeAtom cond +++ " then " ++ serializeAtom l +++ " else " ++ serializeAtom r +++ " end)"+serializeAtom (QExists q) =+ "exists (" ++ serialize q ++ ")"++serializeOp Eq = "="+serializeOp Less = "<"+serializeOp Plus = "<"+serializeOp Minus = "<"+serializeOp Times = "<"+serializeOp Divide = "<"
+ Database/Narc/SQL/Pretty.hs view
@@ -0,0 +1,39 @@+module Database.Narc.SQL.Pretty where++import Database.Narc.Pretty+import Database.Narc.SQL+import Database.Narc.Util (mapstrcat)++instance Pretty Query where+ pretty (Select{rslt=QRecord flds, tabs=tabs, cond=cond}) = + "select " ++ mapstrcat ", " (\(alias, expr) -> + pretty expr ++ " as " ++ alias)+ flds ++ + (if null tabs then "" else+ " from " ++ mapstrcat ", " (\(name, al, ty) -> name ++ " as " ++ al) + tabs) ++ + " where " ++ pretty_cond cond+ where pretty_cond [] = "true"+ pretty_cond cond = mapstrcat " and " pretty cond+ pretty (QOp lhs op rhs) = pretty lhs ++ pretty op ++ pretty rhs+ pretty (QRecord fields) = "{"++ mapstrcat ", "+ (\(lbl,expr) -> + lbl ++ "=" ++ show expr) fields+ ++ "}"+ pretty (QNum n) = show n+ pretty (QBool True) = "true"+ pretty (QBool False) = "false"+ + pretty (QField a b) = a ++ "." ++ b++ pretty (QUnion a b) = pretty a ++ " union all " ++ pretty b+ pretty (QNot b) = "not " ++ pretty b+ pretty (QIf c t f) = "if " ++ pretty c ++ " then " ++ pretty t+ ++ " else " ++ pretty f++-- Pretty-printing for Op, common to both AST and SQL languages.++instance Pretty Op where+ pretty Plus = " + "+ pretty Eq = " = "+ pretty Less = " < "
+ Database/Narc/TermGen.hs view
@@ -0,0 +1,196 @@+module Database.Narc.TermGen where++import Control.Monad hiding (when)++import Test.QuickCheck hiding (promote, Failure)++import Gensym+import QCUtils++import Database.Narc.AST+import qualified Database.Narc.SQL as SQL+import Database.Narc.Type as Type+import Database.Narc.Util++--+-- QuickCheck term generators ------------------------------------------+--++smallIntGen :: Gen Int+smallIntGen = elements [0..5]++typeGen :: [TyVar] -> Int -> Gen Type+typeGen tyEnv size =+ oneof $ [return TBool,+ return TNum+ ] +++ [do x <- elements tyEnv; return $ TVar x | length tyEnv > 0] +++ whens (size > 0)+ [+ do s <- typeGen tyEnv (size-1)+ t <- typeGen tyEnv (size-1)+ return $ TArr s t,+ do t <- typeGen tyEnv (size-1)+ return $ TList t,+ do n <- smallIntGen :: Gen Int+ fields <- sequence [do t <- typeGen tyEnv (size-1)+ return ('f':show i, t) | i <- [0..n]]+ return $ TRecord fields+ ]++-- | Generate a random term, unlikely to be well-typed.+termGen :: [Var] -> Int -> Gen (Term ())+termGen fvs size = frequency $+ [(1, return (Unit, ())),+ (1, do b <- arbitrary; return (Bool b, ())),+ (1, do n <- arbitrary; return (Num n, ()))+ ]+ +++ (whens (not (null fvs)) [(3, do x <- elements fvs;+ return (Var x, ()))])+ +++ whens (size > 0) [+ (3, do x <- varGen+ n <- termGen (x:fvs) (size-1)+ return (Abs x n, ())),+ (6, do m <- termGen fvs (size-1)+ n <- termGen fvs (size-1)+ return $ (App m n, ())),+ (6, do m <- termGen fvs (size-1)+ f <- identGen+ return $ (Project m f, ())),+ (6, do m <- termGen fvs (size-1)+ return $ (Singleton m, ())),+ (18, do n <- smallIntGen+ tableName <- identGen+ fields <- sequence $ replicate n $+ do name <- identGen+ ty <- elements [TBool, TNum]+ return (name, ty)+ return $ (Table tableName fields, ())),+ (9, do n <- smallIntGen+ fields <- sequence $ replicate n $+ do name <- identGen+ term <- termGen fvs (size-1)+ return (name, term)+ return $ (Record fields, ())),+ (72, do x <- varGen -- Overwhelmingly favor comprehensions when+ -- we have enough size remaining, since+ -- we'll be favoring other stuff when we run+ -- out of size.+ l <- termGen fvs (size-1)+ m <- termGen (x:fvs) (size-1)+ return $ (Comp x l m, ()))+ ]++closedTermGen :: Int -> Gen (Term' (), ())+closedTermGen size = + termGen [] size++oneofMaybe :: [Gen(Maybe a)] -> Gen (Maybe a)+oneofMaybe [] = return Nothing+oneofMaybe (x:xs) = do x' <- x+ xs' <- oneofMaybe xs+ case (x', xs') of+ (Nothing, Nothing) -> return Nothing+ _ -> oneof (map (return . Just) $ + asList x' ++ asList xs')++-- Why isn't this bloody thing generating deconstructors??+typedTermGen :: TyEnv -> Type -> Int -> Gen (Term ())+typedTermGen env ty sz = +-- debug ("generating term (type " ++ show ty ++ ") at size " ++ show sz) $+ frequency (+ -- variables+ -- (NOTE: presently only gens vars that have ground type, sans quant'rs)+ [(2, return $ (Var x, ())) | (x, (xQs, xTy)) <- env,+ xQs == [] && xTy == ty] +++ -- constructors+ (case ty of+ TNum -> [(1, do n <- arbitrary; return (Num n, ()))]+ TBool -> [(1, do b <- arbitrary; return (Bool b, ()))]+ TArr s t -> + [(2, do x <- varGen + n <- typedTermGen ((x, ([], s)):(unassoc x env)) t decSz+ return $ (Abs x n, ()))]+ TRecord fTys -> + [(2, do fields <- forM fTys $ \(lbl, ty) ->+ do m <- typedTermGen env ty decSz+ return (lbl, m)+ return $ (Record fields, ()))]+ TList ty ->+ [(2, do m <- typedTermGen env ty decSz + return $ (Singleton m, ()))]+ ++ case ty of + TRecord fTys ->+ if not (and [isBaseTy ty | (_, ty) <- fTys]) then [] else+ [(2, do tab <- identGen+ return $ (Table ('T':tab) fTys, ()))]+ _ -> []+ _ -> error("Strange type while generating term: " ++ + show ty ++ " (size " ++ show sz ++ ")")+ ) +++ -- deconstructors+ if (sz <= 0) then [] else (+ [+ (10, do s <- typeGen [] (intSqrt sz)+ m <- typedTermGen env (TArr s ty) decSz+ n <- typedTermGen env s decSz+ return $ (App m n, ())),+ (10, do c <- typedTermGen env TBool decSz+ a <- typedTermGen env ty decSz+ b <- typedTermGen env ty decSz+ return $ (If c a b, ()))+ ] +++ -- Comprehension: a constructor and a destructor+ case ty of+ (TList _) ->+ [(20, do x <- varGen+ s <- typeGen [] (intSqrt sz)+ src <- typedTermGen env (TList s) decSz+ let env' = Type.bind x ([], s) env+ body <- typedTermGen env' ty decSz+ return (Comp x src body, ()))+ ]+ _ -> []+ )+ )+ where decSz = max (sz-1) 0++closedTypedTermGen :: Type -> Int -> Gen (Term ())+closedTypedTermGen ty size = +-- let tyEnv = runErrorGensym makeInitialTyEnv in+ let tyEnv = [] in+ typedTermGen tyEnv ty size++dbTableTypeGen :: Gen Type+dbTableTypeGen = + do n <- nonNegInt :: Gen Int+ ty <- elements [TBool, TNum]+ return $ TList (TRecord [('t': show i, ty) | i <- [0..n-1]])+++-- Generators++instance Arbitrary SQL.Op where+ arbitrary = oneof [return SQL.Eq, return SQL.Less]++listGen :: Int -> Gen a -> Gen [a]+listGen 0 gen = oneof [ return [], do x <- gen+ xs <- listGen 0 gen+ return (x : xs)]+listGen n gen = do x <- gen+ xs <- listGen (n-1) gen+ return (x : xs)++identCharGen :: Gen Char+identCharGen = oneof $ map return (['a'..'z'] ++ ['A'..'Z'] ++ ['_'])++identGen :: Gen String+identGen = listGen 1 identCharGen++varGen :: Gen String+varGen = (return ('x':)) `ap` identGen++pairGen :: Gen a -> Gen b -> Gen (a, b)+pairGen aGen bGen = do a <- aGen; b <- bGen; return (a, b)
+ Database/Narc/Test.hs view
@@ -0,0 +1,82 @@+{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}++module Database.Narc.Test where++import Prelude hiding (catch)+import Control.Monad.State hiding (when, join)+import Control.Monad.Error ({- Error(..), throwError, -} runErrorT)++import Test.QuickCheck hiding (promote, Failure)+import Test.HUnit hiding (State, assert)++import Gensym+import QCUtils++import Database.Narc.AST+import Database.Narc.Compile+import Database.Narc.Failure+import qualified Database.Narc.SQL as SQL+import Database.Narc.Type as Type+import Database.Narc.TypeInfer+import Database.Narc.TermGen++makeNormalizerTests :: ErrorGensym Test+makeNormalizerTests = + do initialTyEnv <- makeInitialTyEnv + return$ TestList + [TestCase $ unitAssert $ + let term = (Comp "x" (Table "foo" [("fop", TNum)], ())+ (If (Bool True,())+ (Singleton (Record+ [("f0", (Project (Var "x", ())+ "fop",()))],()),())+ (Singleton (Record + [("f0", (Num 3, ()))], ()), ()), + ()), ()) in+ let tyTerm = runErrorGensym $ infer $ term in+ SQL.groundQuery $ compile initialTyEnv $ tyTerm+ ]++unitTests :: ErrorGensym Test+unitTests = do normalizerTests <- makeNormalizerTests + return $ TestList [tyCheckTests, normalizerTests, typingTest]++runUnitTests :: IO Counts+runUnitTests = runErrorGensym $ liftM runTestTT unitTests++--+-- Big QuickCheck properties+--++-- | Assertion that well-typed terms evaluate without throwing.+prop_eval_safe :: Property+prop_eval_safe = + forAll dbTableTypeGen $ \ty ->+ forAll (sized (closedTypedTermGen ty)) $ \m ->+ case tryErrorGensym (infer m) of+ Left _ -> label "ill-typed" $ property True -- ignore ill-typed terms+ -- but report their occurence.+ Right (m'@(_, ty)) -> + isDBTableTy ty ==>+ let q = (compile [] $! m') in+ collect (SQL.sizeQuery q) $ -- NB: Counts sizes only up to ~100.+ excAsFalse (q == q) -- Self-comparison forces the+ -- value (?) thus surfacing+ -- any @error@s that might be+ -- raised.++prop_typedTermGen_tyCheck :: Property+prop_typedTermGen_tyCheck =+ forAll (sized $ typeGen []) $ \ty ->+ forAll (sized $ typedTermGen (runErrorGensym makeInitialTyEnv) ty) $ \m ->+ case runGensym $ runErrorT $ infer m of+ Left _ -> False+ Right (_m', ty') -> isErrorMSuccess $ unify ty ty'++-- Main ----------------------------------------------------------------++main :: IO ()+main = do+ quickCheckWith tinyArgs prop_eval_safe+ _ <- runUnitTests+ return ()
+ Database/Narc/Type.hs view
@@ -0,0 +1,245 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Database.Narc.Type where++import Test.QuickCheck++import Gensym+import QCUtils++import Data.List ((\\))+import Control.Monad.State (State(..), get, put, evalState) -- TBD: use Gensym monad instead+import Control.Applicative ((<$>))+import Database.Narc.Failure (Failure, fayl)+import Database.Narc.Failure.QuickCheck+import Database.Narc.Util (dom, rng, image, alistmap, sortAlist, onCorresponding,+ disjointAlist, validEnv, eqUpTo)+import Database.Narc.Var++type TyVar = Int++data Type = TBool | TNum | TString | TUnit | TList Type+ | TArr Type Type+ | TRecord [(String, Type)]+ | TVar TyVar+ deriving (Eq, Show)++type QType = ([TyVar], Type)++type TySubst = [(Int, Type)]++type TyEnv = [(Var, QType)]++-- Operations on types, rows and substitutions ------------------------++isBaseTy TBool = True+isBaseTy TNum = True+isBaseTy TString = True+isBaseTy _ = False++isTyVar (TVar _) = True+isTyVar _ = False++isDBRecordTy (TRecord fields) = all (isBaseTy . snd) fields+isDBRecordTy _ = False++isRecordTy (TRecord fields) = True+isRecordTy _ = False++isDBTableTy (TList ty) = isDBRecordTy ty+isDBTableTy _ = False++emptyTySubst :: (TySubst)+emptyTySubst = ([])++-- | ftvs: free type variables+ftvs TBool = []+ftvs TNum = []+ftvs TString = []+ftvs TUnit = []+ftvs (TList t) = ftvs t+ftvs (TArr s t) = ftvs s ++ ftvs t+ftvs (TRecord fields) = concat [ftvs t | (lbl, t) <- fields]+ftvs (TVar a) = [a]++numFtvs = length . ftvs++-- | ftvsSubst: the free type variables of a type substitution--that is,+-- the type variables free in the types in the range of the substitution.+ftvsSubst a = concatMap ftvs $ rng a++-- | occurs x ty: does variable x appear in type ty? (Note there are no+-- type-variable binders).+occurs x (TVar y) | x == y = True+ | otherwise = False+occurs x (TArr s t) = x `occurs` s || x `occurs` t+occurs x (TList t) = x `occurs` t+occurs x (TRecord t) = any (occurs x) (map snd t)+occurs x (TUnit) = False+occurs x (TBool) = False+occurs x (TNum) = False+occurs x (TString) = False++applyTySubst :: TySubst -> Type -> Type+applyTySubst subst (TUnit) = TUnit+applyTySubst subst (TBool) = TBool+applyTySubst subst (TNum) = TNum+applyTySubst subst (TString) = TString+applyTySubst subst (TVar a) = case lookup a subst of+ Nothing -> TVar a+ Just ty -> ty+applyTySubst subst (TArr a b) =+ TArr (applyTySubst subst a) (applyTySubst subst b)+applyTySubst subst (TList a) = TList (applyTySubst subst a)+applyTySubst subst (TRecord a) = TRecord (alistmap (applyTySubst subst) a)+++-- Type operations -----------------------------------------------------++instantiate (qs, ty) =+ do subst <- sequence [do y <- gensym ; return (q, TVar y) | q <- qs]+ return $ applyTySubst subst ty++{- | normalizeType:+ Renumber all the type variables in a normal way to allow+ comparing types.+-}+normalizeType :: Type -> State (Int, [(Int, Int)]) Type+normalizeType TBool = return TBool+normalizeType TNum = return TNum+normalizeType TString = return TString+normalizeType TUnit = return TUnit+normalizeType (TList ty) = TList <$> normalizeType ty+normalizeType (TRecord recTy) = undefined+normalizeType (TVar a) = do (ctr, env) <- Control.Monad.State.get+ case lookup a env of+ Nothing -> do put (ctr+1, (a, ctr):env)+ return $ TVar ctr+ Just b -> return $ TVar b+normalizeType (TArr s t) =+ do s' <- normalizeType s+ t' <- normalizeType t+ return $ TArr s' t'++runNormalizeType ty = evalState (normalizeType ty) (0, [])++-- instanceOf: is there a substitution that turns ty2 into ty1? Useful in tests+instanceOf :: Type -> Type -> Failure ()+instanceOf ty1 (TVar x) = return ()+instanceOf TBool TBool = return ()+instanceOf TNum TNum = return ()+instanceOf TString TString = return ()+instanceOf (TArr s t) (TArr u v) = + instanceOf t v >>+ instanceOf s u+instanceOf (TList a) (TList b) = instanceOf a b+instanceOf (TRecord a) (TRecord b) = + let a' = sortAlist a + b' = sortAlist b+ in+ do result <- sequence [if ax == bx then instanceOf ay by else fayl "Record mismatch"+ | ((ax, ay), (bx, by)) <- zip a' b']+ return ()+instanceOf a b = fayl ""++unify :: Type -> Type -> Failure (TySubst)+unify s t | s == t = return ([])+unify (TVar x) t | not (x `occurs` t) = return ([(x, t)])+ | otherwise = fayl("Occurs check failed on " ++ + show (TVar x) ++ " and " ++ show t)+unify t (TVar x) | not (x `occurs` t) = return ([(x, t)])+ | otherwise = fayl("Occurs check failed on " ++ + show t ++ " and " ++ show (TVar x))+unify TBool TBool = return ([])+unify TNum TNum = return ([])+unify TString TString = return ([])+unify (TArr s t) (TArr u v) = + do substSU <- unify s u+ substTV <- unify (applyTySubst substSU t)+ (applyTySubst substSU v)+ composeTySubst [substTV, substSU]+unify (TList a) (TList b) = unify a b+unify (TRecord a) (TRecord b) = + let a' = sortAlist a + b' = sortAlist b+ in+ do substs <- sequence+ [if ax == bx then unify ay by else+ fayl ("Record types " ++ show a' ++ + " and " ++ show b' ++ + " mismatched.")+ | ((ax, ay), (bx, by)) <- zip a' b']+ let (tySubsts) = substs+ subst <- composeTySubst tySubsts+ return (subst)+unify a b = fayl("Type mismatch between " ++ + show a ++ " and " ++ show b)++unifyAll :: [Type] -> Failure TySubst+unifyAll [] = return ([])+unifyAll [x] = return ([])+unifyAll (x1:x2:xs) = do (tySubst) <- x1 `unify` x2+ unifyAll (map (applyTySubst tySubst)+ (x2:xs))++composeTySubst :: [TySubst] -> Failure TySubst+composeTySubst [] = return $ ([])+composeTySubst subst =+ let (tySubsts) = subst in+ do addlSubsts <- sequence $+ onCorresponding unifyAll $ concat tySubsts+ let (addlTySubsts) = addlSubsts+ let substs' = tySubsts ++ addlTySubsts+ let tySubst = flip foldr1 substs'+ (\s1 s2 -> s1 ++ alistmap (applyTySubst s1) s2)+ if any (\(a,b) -> occurs a b) tySubst then + fayl "Circular type substitution in composeTySubst" + else + return (tySubst)++prop_composeTySubst = + forAll (genEnv 0) $ \a ->+ forAll (genEnv (length a)) $ \b ->+ forAll arbitrary $ \ty ->+ disjointAlist a b && validEnv a && validEnv b ==>+ (do ab <- composeTySubst[a, b]+ return $ applyTySubst ab ty)+ == (return $ (applyTySubst a $ applyTySubst b ty) :: Failure Type)++-- unused+disjoinSubst :: TySubst -> TySubst -> TySubst+disjoinSubst a b =+ [(image bx mapping, applyTySubst subst by) | (bx, by) <- b]+ where mapping = (dom b ++ ftvsSubst b) `zip`+ ([0..] \\ (dom a ++ ftvsSubst a))+ subst = alistmap TVar mapping++instance Arbitrary Type where+ arbitrary = + oneof+ [ return TBool+ , return TNum+ , return TString+ , do s <- arbitrary+ t <- arbitrary+ return (TArr s t)+ , do x <- posInt+ return (TVar x)+ ]++-- Check that unification produces a substitution which actually+-- unifies the two types. (Currently fails; something wrong with the way+-- substitutions are composed or not.)+prop_unify_apply_subst = + forAll arbitrary $ \(a :: Type) ->+ forAll arbitrary $ \(b :: Type) -> + collect (numFtvs a, numFtvs b) $+ failureToPropertyIgnoreFailure $+ do (subst) <- a `unify` b+ let e = applyTySubst subst a+ let f = applyTySubst subst b+ return $ eqUpTo runNormalizeType e f++-- { Typing environments } ---------------------------------------------++bind x v env = (x,v):env
+ Database/Narc/TypeInfer.hs view
@@ -0,0 +1,233 @@+module Database.Narc.TypeInfer where++import Data.Maybe (fromMaybe)+import Data.Either++import Control.Monad.State (lift)++import Test.HUnit++import Gensym+import Database.Narc.AST+import Database.Narc.Type+import Database.Narc.Failure+import Database.Narc.Debug (debug)++--+-- Type inference ------------------------------------------------------+--++tyCheckTerms env terms = + do results <- sequence [tyCheck env term | term <- terms]+ let (tySubsts, terms') = unzip results+ let (terms'', termTys) = unzip terms'+ tySubst <- under $ composeTySubst tySubsts+ return (tySubst, terms')++-- | tyCheck env term infers a type for term in environment env.+-- The environment has type [(Var, QType)];+-- an entry (x, qty) indicates that variable x has the quantified type qty;+-- a QType (ys, ty) indicates the type "forall ys, ty".+tyCheck :: TyEnv -> Term a+ -> ErrorGensym (TySubst, Term Type)+tyCheck env (Unit, _) = + do let ty = (TUnit)+ return (emptyTySubst, (Unit, ty))+tyCheck env (Bool b, _) = + do let ty = (TBool)+ return (emptyTySubst, (Bool b, ty))+tyCheck env (Num n, _) = + do let ty = (TNum)+ return (emptyTySubst, (Num n, ty))+tyCheck env (String s, _) = + do let ty = (TString)+ return (emptyTySubst, (String s, ty))+tyCheck env (Table t tys, _) =+ do let ty = (TList (TRecord tys))+ return (emptyTySubst, (Table t tys, ty))+tyCheck env (Var x, _) =+ do let qTy = fromMaybe (error("Type error: no var " ++ x))+ $ lookup x env+ ty <- lift $ instantiate qTy+ debug ("*** instantiated " ++ show qTy ++ " to " ++ show ty) $+ return (emptyTySubst, (Var x, (ty)))+tyCheck env (PrimApp fun args, _) = + do (tySubst, args) <- tyCheckTerms env args+ return(tySubst, (PrimApp fun args, (TBool))) -- TBD+tyCheck env (Abs x n, _) = + do argTyVar <- lift gensym+ (tySubst, n'@(_, (nTy))) <- + tyCheck ((x, ([], TVar argTyVar)) : env) n+ let argTy = applyTySubst tySubst $ TVar argTyVar+ return(tySubst,+ (Abs x n', (TArr argTy nTy)))+tyCheck env (If c a b, _) =+ do (cTySubst, c'@(_, (cTy))) <- tyCheck env c+ (aTySubst, a'@(_, (aTy))) <- tyCheck env a+ (bTySubst, b'@(_, (bTy))) <- tyCheck env b+ cBaseTySubst <- under (unify cTy TBool)+ abTySubst <- under $ unify aTy bTy+ tySubst <- under $ composeTySubst+ [aTySubst, bTySubst, cTySubst,+ cBaseTySubst, abTySubst]+ let ty = applyTySubst tySubst bTy+ return (tySubst,+ (If c' a' b', (ty)))+tyCheck env (Nil, _) = + do t <- lift gensym+ return (emptyTySubst, (Nil, (TList (TVar t))))+tyCheck env (Singleton m, _) =+ do (tySubst, m'@(_, (mTy))) <- tyCheck env m+ return (tySubst,+ (Singleton m', (TList mTy)))+tyCheck env (Union a b, _) =+ do (aTySubst, a'@(_, (aTy))) <- tyCheck env a+ (bTySubst, b'@(_, (bTy))) <- tyCheck env b+ abTySubst <- under $ unify aTy bTy+ tySubst <- under $ composeTySubst+ [aTySubst, bTySubst, abTySubst]+ let ty = applyTySubst tySubst bTy+ return (tySubst,+ (Union a' b', (ty)))+tyCheck env (Record fields, _) =+ let (fieldNames, terms) = unzip fields in+ do (tySubst, terms) <- tyCheckTerms env terms+ let fieldTys = map snd terms+ return (tySubst,+ (Record (zip fieldNames terms),+ (TRecord [(name,ty)| (ty, name) <- zip fieldTys fieldNames])))+tyCheck env (Project m f, _) =+ do rowVar <- lift gensym; a <- lift gensym+ (tySubst, m'@(_, mTy)) <- tyCheck env m+ case mTy of+ TVar x -> -- Note: bogus+ return (((x, TRecord [(f, TVar a)]):tySubst),+ (Project m' f, (TVar a)))+ TRecord fieldTys ->+ case lookup f fieldTys of+ Nothing -> fail("no field " ++ f ++ " in record " ++ + show (strip m))+ Just fieldTy ->+ return (tySubst,+ (Project m' f, fieldTy))+ _ -> fail("Project from non-record type.")+tyCheck env (App m n, _) = + do a <- lift gensym; b <- lift gensym;+ (mTySubst, m'@(_, (mTy))) <- tyCheck env m+ (nTySubst, n'@(_, (nTy))) <- tyCheck env n+ let nTy' = applyTySubst mTySubst $ nTy+ let mTy' = applyTySubst nTySubst $ mTy+ subExprTySubst <- under $ composeTySubst [mTySubst, nTySubst]+ + let mArrTy = TArr (nTy') (TVar b)+ mArrTySubst <- under $ unify mArrTy mTy'+ + let resultTy = applyTySubst mArrTySubst $ TVar b+ + tySubst <- under $ composeTySubst [mArrTySubst,+ subExprTySubst]+ + return (tySubst,+ (App m' n', (resultTy)))++tyCheck env term@(Comp x src body, d) =+ do (substSrc, src') <- tyCheck env src+ let srcTy = typeAnno src'+ a <- lift gensym+ srcTySubst <- under $ unify (TList (TVar a)) srcTy+ let srcTy' = applyTySubst srcTySubst (TVar a)+ (substBody, body') <- tyCheck ((x, unquantType srcTy') : env) body+ let bodyTy = typeAnno body'+ resultSubst <- under $ composeTySubst [substSrc, substBody]+ return (resultSubst, (Comp x src' body', bodyTy))++unquantType ty = ([], ty)++typeAnno :: Term Type -> Type+typeAnno (_, ty) = ty++makeInitialTyEnv :: ErrorGensym [(String, QType)]+makeInitialTyEnv = return []++infer :: Term a -> ErrorGensym TypedTerm -- FIXME broken, discards subst'n+infer term =+ do initialTyEnv <- makeInitialTyEnv+ (_, term') <-+ -- runErrorGensym $ + tyCheck initialTyEnv term+ return term'++infer' :: Term' a -> ErrorGensym TypedTerm+infer' term = infer (term, undefined)++runInfer = runErrorGensym . infer++runTyCheck env m = runErrorGensym $ + do initialTyEnv <- makeInitialTyEnv+ (subst, m') <- tyCheck (initialTyEnv++env) m+ return $ retagulate (applyTySubst subst . snd) m'++inferTys :: Term () -> ErrorGensym Type+inferTys m = + do (_, (ty)) <- infer m+ return (ty)++inferType :: Term () -> ErrorGensym Type+inferType m = infer m >>= (return . snd)++runInferType = runErrorGensym . inferType++inferType' :: Term' () -> ErrorGensym Type+inferType' m = infer' m >>= (return . snd)++-- UNIT TESTS ----------------------------------------------------------++unitAssert b = assertEqual "." b True++tyCheckTests =+ TestList ["Simple application of id to table" ~:+ (runErrorGensym $ + inferTys (App (Abs "x" (Var "x", ()), ())+ (Table "wine" [], ()), ()))+ ~?= (TList (TRecord [])),+ "Curried application of id to table" ~:+ (runErrorGensym . inferTys)+ (App (App+ (Abs "x" (Abs "y" (App (Var "x", ())+ (Var "y", ()), ()), ()), ())+ (Abs "x" (Var "x", ()), ()), ())+ (Table "wine" [], ()), ())+ ~?= (TList (TRecord [])),+ "Curried application, de/reconstructing record" ~:+ (runErrorGensym . inferTys) + (App (App+ (Abs "f" (Abs "x" (App (Var "f",()) (Var "x",()),()),()),())+ (Abs "x"+ (Record[("baz", (Project(Var "x",()) "foo", ()))],+ ()),+ ()), ())+ (Record [("foo", (Num 47, ()))], ()), ())+ ~?= (TRecord[("baz", TNum)]),+ "omega" ~:+ unitAssert $ isError $+ (tryErrorGensym . inferType)+ (Abs "x" (App (Var "x", ()) (Var "x", ()), ()), ())+ ]++typingTest1 = + let idTy = (TArr (TVar 9) (TVar 9)) in+ let concatMapTy = (TArr (TArr (TVar 2) (TList (TVar 3)))+ (TArr (TList (TVar 2))+ (TList (TVar 3)))) in+ let Right mArrSubst = unify concatMapTy (TArr (TVar 4) (TVar 5)) in+ let argTy = applyTySubst mArrSubst (TVar 4) in+ -- TArr (TVar 2) ([],Just 0) (TList (TVar 3))+ let Right funcArgSubst = unify argTy idTy in+ let resultTy = (applyTySubst funcArgSubst $ applyTySubst mArrSubst (TVar 5)) + in+ (resultTy, funcArgSubst,+ case resultTy of+ TArr (TList (TList (TVar a))) (TList (TVar b)) -> a == b)++typingTest = let (_,_,x) = typingTest1 in + TestCase (unitAssert x)
+ Database/Narc/Util.hs view
@@ -0,0 +1,111 @@+module Database.Narc.Util where++import Data.Maybe (fromJust, isJust)+import Data.List as List ((\\), nub, intersperse, groupBy, sortBy, sort)++--+-- List Utilities+--++dom alist = map fst alist+rng alist = map snd alist++collate proj = groupBy (\x y -> proj x == proj y) . + sortBy (\x y -> proj x `compare` proj y)++sortAlist :: [(String, b)] -> [(String, b)]+sortAlist = sortBy (\a b -> fst a `compare` fst b)++cross f g (x,y) = (f x, g y)++onRight f = cross id f+onLeft f = cross f id++-- | shadow: given two alists, return the elements of the first that+-- are NOT mapped by the second+shadow as bs = [(a,x) | (a,x) <- as, a `notElem` domBs]+ where domBs = map fst bs++-- | Tests that an alist or environment is well-formed: that its first+-- | components are all unique.+validEnv xs = length (nub $ map fst xs) == length xs++mr agg xs = map reduceGroup (collate fst xs)+ where reduceGroup xs = let (as, bs) = unzip xs in+ (the as, agg bs)+ the xs | allEq xs = head xs++onCorresponding :: Ord a => ([b]->c) -> [(a,b)] -> [c]+onCorresponding agg xs = map reduceGroup (collate fst xs)+ where reduceGroup xs = agg $ map snd xs++($>) x f = f x++image x = fromJust . lookup x++maps x = isJust . lookup x++intSqrt :: Integral a => a -> a+intSqrt = floor . sqrt . fromIntegral++unassoc a = filter ((/= a) . fst)++nubassoc [] = []+nubassoc ((x,y):xys) = (x,y) : (nubassoc $ unassoc x xys)++graph f xs = [(x, f x) | x <- xs]+alistmap f = map (\(a, b) -> (a, f b))++bagEq a b = a \\ b == [] && b \\ a == []++setEq a b = (nub a) `bagEq` (nub b)++u a b = nub (a ++ b)++contains a b = null(b \\ a)++setMinus xs ys = nub $ sort $ xs \\ ys++(\\\) xs ys = setMinus xs ys++allEq [] = True+allEq (x:xs) = all (== x) xs++disjoint :: Eq a => [a] -> [a] -> Bool+disjoint xs ys = not (any (\x-> any (==x) ys) xs)++disjointAlist xs ys = disjoint (map fst xs) (map fst ys)+-- disjointAlist [] _ = True+-- disjointAlist _ [] = True+-- disjointAlist xs ((a,b):ys) =+-- (not $ any ((== a) . fst) xs) && disjointAlist xs ys++-- | Convert a maybe to a zero-or-one-element list.+asList :: Maybe a -> [a]+asList Nothing = []+asList (Just x) = [x]++isRight :: Either a b -> Bool+isRight (Right _) = True+isRight (Left _ ) = False++isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft (Right _ ) = False++-- | zipAlist: given two alists with the same domain,+-- returns an alist mapping each of those domain values to+-- the pair of the two corresponding values from the given lists.+zipAlist xs ys = + let xsys = zip (sortAlist xs) (sortAlist ys) in+ if not $ and [x == y | ((x, a), (y, b)) <- xsys] then + error "alist mismatch in zipAlist"+ else [(x, (a, b)) | ((x, a), (y, b)) <- xsys]++-- | mapstrcat: transform a list to one of strings, with a given+-- | function, and join these together with some `glue' string.+mapstrcat :: String -> (a -> String) -> [a] -> String+mapstrcat glue f xs = concat $ List.intersperse glue (map f xs)++-- Functional utilities+eqUpTo f x y = f x == f y
+ Database/Narc/Var.hs view
@@ -0,0 +1,4 @@+module Database.Narc.Var where++type Var = String+
− Narc.hs
@@ -1,200 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}-{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}---- | Query SQL databases using Nested Relational Calculus embedded in--- Haskell.--- --- The primed functions in this module are in fact the syntactic --- forms of the embedded language. Use them as, for example:--- --- > foreach (table "employees" []) $ \emp ->--- > having (primApp "<" [cnst 20000, project emp "salary"]) $--- > singleton (record [(project emp "name")])--module Narc (- -- * The type of the embedded terms- NarcTerm,- -- * Translation to an SQL representation- narcTermToSQL,- -- * The language itself- unit, Const, primApp, abs, app, ifthenelse, singleton,- nil, union, record, project, foreach, having-) where--import Prelude hiding (abs, catch)-import Control.Exception (catch, throwIO, evaluate, SomeException)-import Control.Monad.State hiding (when, join)-import Control.Monad.Error (throwError, runErrorT, Error(..))-import Data.List (nub, (\\), sort, sortBy, groupBy, intersperse)-import Data.Maybe (fromJust, isJust, fromMaybe)--import Control.Applicative ((<$>), (<*>))-import Foreign (unsafePerformIO) -- FIXME--import Test.QuickCheck hiding (promote, Failure)-import QCUtils-import Test.HUnit hiding (State, assert)--import Debug.Trace--import Gensym--import Narc.AST-import Narc.Common-import Narc.Compile-import Narc.Debug-import Narc.Eval-import Narc.Failure-import Narc.Pretty-import Narc.AST.Pretty-import Narc.SQL.Pretty-import qualified Narc.SQL as SQL-import Narc.Type as Type-import Narc.TypeInfer-import Narc.Util--import Narc.HDBC---- THE AWESOME FULL COMPILATION FUNCTION ---------------------------------typeCheckAndCompile :: Term a -> SQL.Query-typeCheckAndCompile = compile [] . runTyCheck []---- The Narc embedded langauge----------------------------------------------- Example query--example_dull = (Comp "x" (Table "foo" [("a", TBool)], ())- (If (Project (Var "x", ()) "a", ())- (Singleton (Var "x", ()), ())- (Nil, ()), ()), ())---- HOAS-ish embedded language.--type NarcTerm = Gensym (Term ()) -- ^ Bleck. Rename.---- | Translate a Narc term to an SQL query string--perhaps the central--- | function of the interface.-narcTermToSQLString :: NarcTerm -> String-narcTermToSQLString = SQL.serialize . narcTermToSQL---- | Translate a Narc term to an SQL query.-narcTermToSQL :: NarcTerm -> SQL.Query-narcTermToSQL = typeCheckAndCompile . realize---- | Turn a HOAS representation of a Narc term into a concrete,--- | named-binder representation.-realize :: NarcTerm -> Term ()-realize = runGensym---- | A dummy value, or zero-width record.-unit :: NarcTerm-unit = return $ (!) Unit---- | A polymorphic way of embedding constants into a term.-class Const' a where cnst' :: a -> NarcTerm-instance Const' Bool where cnst' b = return ((!)(Bool b))-instance Const' Integer where cnst' n = return ((!)(Num n))---- | Apply some primitive function, such as @(+)@ or @avg@, to a list--- of arguments.-primApp :: String -> [NarcTerm] -> NarcTerm-primApp f args = (!) . PrimApp f <$> sequence args---- | Create a functional abstraction.-abs :: (String -> NarcTerm) -> NarcTerm-abs fn = do- n <- gensym- let x = '_' : show n- body <- fn x- return $ (!) $ Abs x body---- | Apply a functional term to an argument.-app :: NarcTerm -> NarcTerm -> NarcTerm-app l m = (!) <$> (App <$> l <*> m)---- | A reference to a named database table; second argument is its--- schema type.-table :: Tabname -> [(Field, Type)] -> NarcTerm-table tbl ty = return $ (!) $ Table tbl ty---- | A condition between two terms, as determined by the boolean value--- of the first term.-ifthenelse :: NarcTerm -> NarcTerm -> NarcTerm -> NarcTerm-ifthenelse c t f = (!) <$> (If <$> c <*> t <*> f)---- | A singleton collection of one item.-singleton :: NarcTerm -> NarcTerm-singleton x = (!) . Singleton <$> x---- | An empty collection.-nil :: NarcTerm-nil = return $ (!) $ Nil---- | The union of two collections-union :: NarcTerm -> NarcTerm -> NarcTerm-union l r = (!) <$> (Union <$> l <*> r)---- | Construct a record (name-value pairs) out of other terms; usually--- used, with base values for the record elements, as the final--- result of a query, corresponding to the @select@ clause of a SQL--- query, but can also be used with nested results internally in a--- query.-record :: [(String, NarcTerm)] -> NarcTerm-record fields = (!) <$> (Record <$> sequence [do expr' <- expr ; return (lbl, expr') | (lbl, expr) <- fields])---- | Project a field out of a record value.-project :: NarcTerm -> String -> NarcTerm-project expr field = (!) <$> (Project <$> expr <*> return field)---- | For each item in the collection resulting from the first--- argument, give it to the function which is the second argument--- and evaluate--this corresponds to a loop, or two one part of a--- cross in traditional SQL queries.-foreach :: NarcTerm -> (NarcTerm -> NarcTerm) -> NarcTerm-foreach src k = do- src' <- src- n <- gensym- let x = '_' : show n- body' <- k (return (var_ x))- return $ (!)(Comp x src' body')---- | Filter the current iteration as per the condition in the first--- argument. Corresponds to a @where@ clause in a SQL query.-having :: NarcTerm -> NarcTerm -> NarcTerm-having cond body = ifthenelse cond body nil---- Example query--example' = let t = (table "foo" [("a", TBool)]) in- foreach t $ \x -> - (having (project x "a")- (singleton x))--example2' = let t = (table "foo" [("a", TNum)]) in- let s = (table "bar" [("a", TNum)]) in- foreach t $ \x -> - foreach s $ \y -> - ifthenelse (primApp "<" [project x "a", project y "a"])- (singleton x)- (singleton y)--example3' =- let t = table "employees" [("name", TString), ("salary", TNum)] in- foreach t $ \emp ->- having (primApp "<" [cnst' (20000::Integer), project emp "salary"]) $- singleton (record [("nom", project emp "name")])---- Unit tests ------------------------------------------------------------test_example =- TestList [- SQL.serialize (typeCheckAndCompile (realize example'))- ~?= "select _0.a as a from foo as _0 where _0.a"- ,- SQL.serialize (typeCheckAndCompile (realize example2'))- ~?= "(select _0.a as a from foo as _0, bar as _1 where _0.a < _1.a) union (select _1.a as a from foo as _0, bar as _1 where not(_0.a < _1.a))"- ,- SQL.serialize (typeCheckAndCompile (realize example3'))- ~?= "select _0.name as nom from employees as _0 where 20000 < _0.salary"- ]
− Narc/AST.hs
@@ -1,298 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}--module Narc.AST (- Term'(..),- Term,- Var,- PlainTerm,- TypedTerm,- fvs,- substTerm,- strip,- retagulate,- rename,- variables,- (!),- unit_, Const, cnst_, primApp_, var_, abs_, app_, table_, ifthenelse_,- singleton_, nil_, union_, record_, project_, foreach_ -) where--import Data.List as List ((\\), nub)--import Prelude hiding (abs)--import Narc.Common-import Narc.Type-import Narc.Util (alistmap, u)-import Narc.Var---- | Terms in the nested relational calculus (represented concretely--- | with named variables)-data Term' a = Unit | Bool Bool | Num Integer | String String- | PrimApp String [Term a]- | Var Var | Abs Var (Term a) | App (Term a) (Term a)- | Table Tabname [(Field, Type)]- | If (Term a) (Term a) (Term a)- | Singleton (Term a) | Nil | Union (Term a) (Term a)- | Record [(String, Term a)]- | Project (Term a) String- | Comp Var (Term a) (Term a)--- | IsEmpty (Term a)- deriving (Eq,Show)---- | Terms whose every subexpression is annotated with a value of some--- | particular type.-type Term a = (Term' a, a)---- TBD: use term ::: type or similar instead of (term, type).--type PlainTerm = Term ()--type TypedTerm = Term Type---- Operations on terms ---------------------------------------------------fvs (Unit, _) = []-fvs (Bool _, _) = []-fvs (Num _, _) = []-fvs (String _, _) = []-fvs (PrimApp prim args, _) = nub $ concat $ map fvs args-fvs (Var x, _) = [x]-fvs (Abs x n, _) = fvs n \\ [x]-fvs (App l m, _) = fvs l `u` fvs m-fvs (Table _ _, _) = []-fvs (If c a b, _) = fvs c `u` fvs a `u` fvs b-fvs (Nil, _) = []-fvs (Singleton elem, _) = fvs elem-fvs (Union m n, _) = fvs m `u` fvs n-fvs (Record fields, _) = nub $ concat $ map (fvs . snd) fields-fvs (Project targ _, _) = fvs targ-fvs (Comp x src body, _) = fvs src `u` (fvs body \\ [x])--variables = map ('y':) $ map show [0..]--rename x y (Var z, q) | x == z = (Var y, q)- | otherwise = (Var z, q)-rename x y (l@(Abs z n, q)) | x == z = l- | otherwise = (Abs z (rename x y n), q)-rename x y (App l m, q) = (App (rename x y l) (rename x y m), q)-rename x y (PrimApp prim args, q) = (PrimApp prim (map (rename x y) args), q)-rename x y (Singleton elem, q) = (Singleton (rename x y elem), q)-rename x y (Project targ label, q) = (Project (rename x y targ) label, q)-rename x y (Record fields, q) = (Record (alistmap (rename x y) fields), q)-rename x y (Comp z src body, q) - | x == z = (Comp z src body, q)- | y == z = let y' = head $ variables \\ [y] in- let body' = rename y y' body in- (Comp z (rename x y src) (rename x y body'), q)- | otherwise= (Comp z (rename x y src) (rename x y body), q)-rename x y (String n, q) = (String n, q)-rename x y (Bool b, q) = (Bool b, q)-rename x y (Table s t, q) = (Table s t, q)-rename x y (If c a b, q) = (If (rename x y c) (rename x y a) (rename x y b), q)-rename x y (Unit, q) = (Unit, q)-rename x y (Nil, q) = (Nil, q)-rename x y (Union a b, q) = (Union (rename x y a) (rename x y b), q)---- | substTerm x v m: substite v for x in term m--- (Actually incorrect because it does not make substitutions in the q.)-substTerm :: Var -> Term t -> Term t -> Term t-substTerm x v (m@(Unit, _)) = m-substTerm x v (m@(Bool b, _)) = m-substTerm x v (m@(Num n, _)) = m-substTerm x v (m@(String s, _)) = m-substTerm x v (m@(Table s t, _)) = m-substTerm x v (m@(Nil, _)) = m-substTerm x v (Singleton elem, q) = (Singleton (substTerm x v elem), q)-substTerm x v (Union m n, q) = (Union (substTerm x v m) (substTerm x v n), q)-substTerm x v (m@(Var y, _)) | y == x = v- | otherwise = m-substTerm x v (l @ (Abs y n, q))- | x == y = l- | y `notElem` fvs v = (Abs y (substTerm x v n), q) - | otherwise = - let y' = head $ variables \\ fvs v in- let n' = rename y y' n in- (Abs y' (substTerm x v n'), q)-substTerm x v (App l m, q) = (App (substTerm x v l) (substTerm x v m), q)-substTerm x v (PrimApp prim args,q)= (PrimApp prim (map (substTerm x v) args),q)-substTerm x v (Project targ label, q) = (Project (substTerm x v targ) label, q)-substTerm x v (Record fields, q) = (Record (alistmap (substTerm x v) fields), q)-substTerm x v (Comp y src body, q) - | x == y =- (Comp y src' body, q)- | y `notElem` fvs v =- (Comp y src' (substTerm x v body), q)- | otherwise = - let y' = head $ variables \\ fvs v in- let body' = rename y y' body in- (Comp y' src' (substTerm x v body'), q)- where src' = (substTerm x v src)-substTerm x v (If c a b, q) = - (If (substTerm x v c) (substTerm x v a) (substTerm x v b), q)---- | lazyDepth: calculate a list (poss. inf.) whose sum is the depth--- of the term. (unused)-lazyDepth :: Term a -> [Int]-lazyDepth (Abs _ n, _) = 1 : lazyDepth n-lazyDepth (App l m, _) = 1 : zipWith max (lazyDepth l) (lazyDepth m)-lazyDepth (Project m _, _) = 1 : lazyDepth m-lazyDepth (Singleton m, _) = 1 : lazyDepth m-lazyDepth (PrimApp prim args, _) =- 1 : foldr1 (zipWith max) (map lazyDepth args)-lazyDepth (Record fields, _) =- 1 : foldr1 (zipWith max) (map (lazyDepth . snd) fields)-lazyDepth (Comp _ src body, _) =- 1 : zipWith max (lazyDepth src) (lazyDepth body)-lazyDepth _ = 1 : []---- Generic term-recursion functions --------------------------------------entagulate :: (Term a -> b) -> Term a -> Term b-entagulate f (Bool b, d) = (Bool b, f (Bool b, d))-entagulate f (Num n, d) = (Num n, f (Num n, d))-entagulate f (String s, d) = (String s, f (String s, d))-entagulate f (Var x, d) = (Var x, f (Var x, d))-entagulate f (Abs x n, d) = (Abs x (entagulate f n), f (Abs x n, d))-entagulate f (App l m, d) = (App (entagulate f l) (entagulate f m),- f (App l m, d))-entagulate f (If c a b, d) =- (If (entagulate f c)- (entagulate f a)- (entagulate f b),- f (If c a b, d))-entagulate f (Table tab fields, d) = (Table tab fields, f (Table tab fields, d))-entagulate f (Nil, d) = (Nil, f (Nil,d))-entagulate f (Singleton m, d) = (Singleton (entagulate f m),- f (Singleton m, d))-entagulate f (Union a b, d) =- (Union- (entagulate f a)- (entagulate f b),- f (Union a b, d))-entagulate f (Record fields, d) = (Record (alistmap (entagulate f) fields), - f (Record fields, d))-entagulate f (Project m a, d) = (Project (entagulate f m) a,- f (Project m a, d))-entagulate f (Comp x src body, d) = - (Comp x (entagulate f src) (entagulate f body),- f (Comp x src body, d))--retagulate :: (Term a -> a) -> Term a -> Term a-retagulate f (Unit, d) = (Unit, f (Unit, d))-retagulate f (Bool b, d) = (Bool b, f (Bool b, d))-retagulate f (Num n, d) = (Num n, f (Num n, d))-retagulate f (String s, d) = (String s, f (String s, d))-retagulate f (Var x, d) = (Var x, f (Var x, d))-retagulate f (Abs x n, d) = (Abs x (retagulate f n),- f (Abs x (retagulate f n), d))-retagulate f (App l m, d) = (App (retagulate f l) (retagulate f m),- f (App (retagulate f l) (retagulate f m), d))-retagulate f (PrimApp fn ar, d) = (PrimApp fn (map (retagulate f) ar),- f (PrimApp fn (map (retagulate f) ar), d))-retagulate f (If c a b, d) =- (If (retagulate f c)- (retagulate f a)- (retagulate f b),- f (If (retagulate f c)- (retagulate f a)- (retagulate f b), d))-retagulate f (Table tab fields, d) = (Table tab fields, f (Table tab fields, d))-retagulate f (Nil, d) = (Nil, f (Nil, d))-retagulate f (Singleton m, d) = (Singleton (retagulate f m),- f (Singleton (retagulate f m), d))-retagulate f (Union l m, d) = (Union (retagulate f l) (retagulate f m),- f (Union (retagulate f l) (retagulate f m), d))-retagulate f (Record fields, d) = (Record (alistmap (retagulate f) fields), - f (Record (alistmap (retagulate f) fields), d))-retagulate f (Project m a, d) = (Project (retagulate f m) a,- f (Project (retagulate f m) a, d))-retagulate f (Comp x src body, d) = - (Comp x (retagulate f src) (retagulate f body),- f (Comp x (retagulate f src) (retagulate f body), d))--strip = entagulate (const ())---- | numComps: Number of comprehensions in an expression, a measure of--- the complexity of the query.-numComps (Comp x src body, _) = 1 + numComps src + numComps body-numComps (PrimApp _ args, _) = sum $ map numComps args-numComps (Abs _ n, _) = numComps n-numComps (App l m, _) = numComps l + numComps m-numComps (Singleton body, _) = numComps body-numComps (Record fields, _) = sum $ map (numComps . snd) fields-numComps (Project m _, _) = numComps m-numComps (Union a b, _) = numComps a + numComps b-numComps (Unit, _) = 0-numComps (Bool _, _) = 0-numComps (Num _, _) = 0-numComps (String _, _) = 0-numComps (Var _, _) = 0-numComps (Table _ _, _) = 0-numComps (If c a b, _) = numComps c + numComps a + numComps b-numComps (Nil, _) = 0---- | An interface for semanticizing the Narc concrete language as--- | desired (as per "Unembedding domain specific languages" by Atkey,--- | Lindley and Yallop).-class NarcSem result where- unit :: result- bool :: Bool -> result- num :: Integer -> result- string :: String -> result- primApp :: String -> [result] -> result- var :: Var -> result- abs :: Var -> result -> result- app :: result -> result -> result- table :: Tabname -> [(Field, Type)] -> result- ifthenelse :: result -> result -> result -> result- singleton :: result -> result- nil :: result- union :: result -> result -> result- record :: [(String, result)] -> result- project :: result -> String -> result- foreach :: result -> Var -> result -> result--- cnst :: Constable t => t -> result-class Constable t where cnst :: NarcSem result => t -> result-instance Constable Bool where cnst b = bool b-instance Constable Integer where cnst n = num n---- Explicit-named builders--(!) x = (x, ())--instance NarcSem (Term'(),()) where- unit = (!)Unit- bool b = (!)(Bool b)- num n = (!)(Num n)- string n = (!)(String n)- primApp f args = (!)(PrimApp f args)- var x = (!)(Var x)- abs x body = (!)(Abs x body)- app l m = (!)(App l m)- table tbl ty = (!)(Table tbl ty)- ifthenelse c t f = (!)(If c t f)- singleton x = (!)(Singleton x)- nil = (!)Nil- union a b = (!)(Union a b)- record fields = (!)(Record fields)- project body field = (!)(Project body field)- foreach src x body = (!)(Comp x src body)--- class Const a where cnst_ :: a -> Term ()--unit_ = (!)Unit-class Const a where cnst_ :: a -> Term ()-instance Const Bool where cnst_ b = (!)(Bool b)-instance Const Integer where cnst_ n = (!)(Num n)-primApp_ f args = (!)(PrimApp f args)-var_ x = (!)(Var x)-abs_ x body = (!)(Abs x body)-app_ l m = (!)(App l m)-table_ tbl ty = (!)(Table tbl ty)-ifthenelse_ c t f = (!)(If c t f)-singleton_ x = (!)(Singleton x)-nil_ = (!)Nil-union_ a b = (!)(Union a b)-record_ fields = (!)(Record fields)-project_ body field = (!)(Project body field)-foreach_ src x body = (!)(Comp x src body)
− Narc/AST/Pretty.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE TypeSynonymInstances #-}--module Narc.AST.Pretty where--import Narc.AST-import Narc.Pretty-import Narc.Util (mapstrcat)---- Pretty-printing ------------------------------------------------=====--instance Pretty (Term' a) where- pretty (Unit) = "()"- pretty (Bool b) = show b- pretty (Num n) = show n- pretty (PrimApp f args) = f ++ "(" ++ mapstrcat "," pretty args ++ ")"- pretty (Var x) = x- pretty (Abs x n) = "(fun " ++ x ++ " -> " ++ pretty n ++ ")"- pretty (App l m) = pretty l ++ " " ++ pretty m- pretty (Table tbl t) = "(table " ++ tbl ++ " : " ++ show t ++ ")"- pretty (If c a b) =- "(if " ++ pretty c ++ " then " ++ pretty a ++ - " else " ++ pretty b ++ " )"- pretty (Singleton m) = "[" ++ pretty m ++ "]" - pretty (Nil) = "[]"- pretty (Union m n) = "(" ++ pretty n ++ " ++ " ++ pretty n ++ ")"- pretty (Record fields) = - "{" ++ mapstrcat "," (\(l,m) -> l ++ "=" ++ pretty m) fields ++ "}"- pretty (Project m l) = "(" ++ pretty m ++ "." ++ l ++ ")"- pretty (Comp x m n) =- "(for (" ++ x ++ " <- " ++ pretty m ++ ") " ++ pretty n ++ ")"--instance Pretty (Term a) where- pretty (m, t) = pretty m
− Narc/Common.hs
@@ -1,6 +0,0 @@-module Narc.Common where--type Tabname = String--type Field = String-
− Narc/Compile.hs
@@ -1,226 +0,0 @@-{-# OPTIONS_GHC -Wall #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Narc.Compile (compile) where--import Data.List ((\\))--import Narc.AST-import Narc.AST.Pretty ()-import Narc.Contract-import Narc.Debug (forceAndReport)-import Narc.Pretty-import Narc.SQL-import Narc.Type as Type-import Narc.TypeInfer-import Narc.Util (image, maps, alistmap)---- -- Testing-related imports--- import Test.QuickCheck (Property, forAll, sized)--- import Narc.TermGen--- import Narc.Eval--- import Narc.Failure---- { Compilation } -------------------------------------------------------etaExpand :: TypedTerm -> [(String, Type)] -> TypedTerm-etaExpand expr fieldTys =- let exprTy = TRecord fieldTys in- (Record [(field, ((Project expr field), fTy))- | (field, fTy) <- fieldTys], - exprTy)---- | Normalize DB terms in a nearly call-by-value way.-normTerm :: [(String, QType)] -- ^ An environment, typing all free vars.- -> TypedTerm -- ^ The term to normalize.- -> TypedTerm-normTerm _env (m@(Unit, _ty)) = m-normTerm _env (m@(Bool _, _)) = m-normTerm _env (m@(Num _, _)) = m-normTerm _env (m@(String _, _)) = m-normTerm env (PrimApp fun args, t) = (PrimApp fun (map (normTerm env) args), t)-normTerm env (expr@(Var x, t)) = - -- Eta-expand at record type.- if (maps x) env then - case t of- TRecord t' -> etaExpand expr t'- _ -> (Var x, t) - else- error $ "Free variable "++ x ++ " in normTerm"-normTerm _env (Abs x n, t) =- (Abs x n, t)-normTerm env (App l m, t) = - let w = normTerm env m in- case normTerm env l of - (Abs x n, _) -> - forceAndReport (- let !n' = substTerm x w n in- normTerm env (runTyCheck env $ n')- ) ("susbtituting "++show w++" for "++x++" in "++show n)- (If b l1 l2, _) ->- (normTerm env (If b (App l1 w, t) (App l2 w, t), t))- v@(Var _, _) -> (App v w, t)- v -> error $ "unexpected normal form in appl posn in normTerm " ++ show v-normTerm _env (Table s t, t') = (Table s t, t')-normTerm env (If b m (Nil, _), t@(TList _)) =- let b' = normTerm env b in- case normTerm env m of- (Nil, _) -> (Nil, t)- (Singleton m', _) -> (If b' (Singleton m', t) (Nil, t), t)- (Table s fTys, _) -> (If b' (Table s fTys, t) (Nil, t), t)- (Comp x l m', _) -> normTerm env (Comp x l (If b' m' (Nil, t), t), t)- (m1 `Union` m2, _) -> ((normTerm env (If b' m1 (Nil, t), t)) `Union`- (normTerm env (If b' m2 (Nil, t), t)), t)- v@(If _ _ _, _) -> (If b' v (Nil, t), t)- v -> error $ "Unexpected normal form in conditional body in normTerm: " ++- show v-normTerm env (If b@(_,bTy) m n, t@(TList _)) = -- The case where n /= Nil- ((normTerm env (If b m (Nil, t), t)) `Union` - (normTerm env (If (PrimApp "not" [b], bTy) n (Nil, t), t)), t)-normTerm env (If b m n, t@(TRecord fTys)) =- let b' = normTerm env b in- let (Record mFields, _) = normTerm env m- (Record nFields, _) = normTerm env n in- (Record [(l, (If b' (image l mFields) (image l nFields), (image l fTys)))- | (l, _) <- mFields],- t)-normTerm env (If b m n, t) = - (If (normTerm env b) (normTerm env m) (normTerm env n), t)-normTerm env (Singleton m, t) = (Singleton (normTerm env m), t)-normTerm _env (Nil, t) = (Nil, t)-normTerm env (m `Union` n, t) = ((normTerm env m) `Union` (normTerm env n), t)-normTerm env (Record fields, t) =- (Record [(a, normTerm env m) | (a, m) <- fields], t)-normTerm env (Project argTerm label, t) = - case normTerm env argTerm of- (Record fields, _) -> case (lookup label fields) of - Just x -> x - Nothing -> error $ "no field " ++ label- -- Ah, the following not necessary because If pushes into records.- (If condn v1 v2,_) ->- normTerm env (If condn- (Project v1 label, t)- (Project v2 label, t), t)- v@(Var _x, _) -> (Project v label, t)- v -> error $ "Unexpected normal form in body of Project in normTerm: " ++ - show v-normTerm env (Comp x src body, t) =- case normTerm env src of- (Nil, _) -> (Nil, t)- (Singleton src', _) -> - forceAndReport (- let !n' = substTerm x src' body in- normTerm env (runTyCheck env n')- ) ("Substituting " ++ show src' ++ " for " ++ x ++ " in " ++ show body)- (Comp y src2 body2, _) ->- -- Freshen @y@ over @src@ with respect to @body@ (that of- -- the outer comprehension), because we're widening the- -- scope of @y@ to include @body@.- let (y', body') = if y `elem` fvs body then- let newY = minFreeFor body in- (newY, rename y newY body)- else (y, body)- in- (normTerm env (Comp y' src2 (Comp x body2 body', t), t))- (srcL `Union` srcR, _) ->- ((normTerm env (Comp x srcL body, t)) `Union` - (normTerm env (Comp x srcR body, t)), t)- (tbl @ (Table _tableName fieldTys, _)) ->- insert (\(v',t') -> (Comp x tbl (v',t'), t')) $- let env' = Type.bind x ([],TList(TRecord fieldTys)) env in - normTerm env' body- (If cond' src' (Nil, _), _) ->- assert (x `notElem` fvs cond') $- let v = normTerm env (Comp x src' body, t) in- insertFurther (\(v',t') -> (If cond' (v',t') (Nil, t'), t')) v- v -> error $- "unexpected normal form in source part of comprehension: " ++- show v---- Insertion functions for rebuilding a term, dropping a--- reconstructor k down through unions and compr'ns (there must be--- a better way!).-insert :: (TypedTerm -> TypedTerm) -> TypedTerm -> TypedTerm-insert k ((v,t) :: TypedTerm) =- case v of- Nil -> (Nil, t)- n1 `Union` n2 -> ((insert k n1) `Union` (insert k n2), t)- _ -> k (v,t)--insertFurther :: (TypedTerm -> TypedTerm) -> TypedTerm -> TypedTerm-insertFurther k ((v,t) :: TypedTerm) =- case v of- Nil -> (Nil, t)- n1 `Union` n2 -> - ((insertFurther k n1) `Union` (insertFurther k n2), t)- Comp x m n -> (Comp x m (insertFurther k n), t)- _ -> k (v,t)---- See (Bird 2010) for a better algorithm here.-minFreeFor :: Term a -> Var-minFreeFor n = head $ variables \\ fvs n ---- | @translateTerm@ homomorphically translates a normal-form Term to an--- | SQL Query.-translateTerm :: TypedTerm -> Query-translateTerm (v `Union` u, _) = (translateTerm v) `QUnion` (translateTerm u)-translateTerm (Nil, _) = Narc.SQL.emptyQuery-translateTerm (f@(Comp _ (Table _ _, _) _, _)) = translateF f-translateTerm (f@(If _ _ (Nil, _), _)) = translateF f-translateTerm (f@(Singleton (Record _, _), _)) = translateF f-translateTerm (f@(Table _ _, _)) = translateF f-translateTerm x = - error $ "translateTerm got unexpected term: " ++ (pretty.fst) x---- translateF, translateZ and translateB are named after the syntactic--- classes (in the grammar of the normalized form) which they handle.--- (F for "for comprehension", Z for "final bit of a nest of--- comprehensions", and B for "base type"-translateF :: Term b -> Query-translateF (Comp x (Table tabname fTys, _) n, _) =- let q@(Select _ _ _) = translateF n in- Select {rslt = rslt q,- tabs = (tabname, x, TRecord fTys):tabs q,- cond = cond q}-translateF (z@(If _ _ (Nil, _), _)) = translateZ z-translateF (z@(Singleton (Record _, _), _)) = translateZ z-translateF (z@(Table _ _, _)) = translateZ z-translateF m = error $ "translateF for unexpected term: " ++ pretty (fst m)--translateZ :: Term b -> Query-translateZ (If b z (Nil, _), _) =- let q@(Select _ _ _) = translateZ z in- Select {rslt=rslt q, tabs = tabs q, cond = translateB b : cond q}-translateZ (Singleton (Record fields, _), _) = - Select {rslt = QRecord(alistmap translateB fields), tabs = [], cond = []}-translateZ (Table tabname fTys, _) =- Select {rslt = QRecord[(l,QField tabname l)| (l,_ty) <- fTys],- tabs = [(tabname, tabname, TRecord fTys)], cond = []}-translateZ z = error$ "translateZ got unexpected term: " ++ (pretty.fst) z--translateB :: Term b -> Query-translateB (If b b' b'', _) = QIf (translateB b)- (translateB b') (translateB b'') -translateB (Bool n, _) = (QBool n)-translateB (Num n, _) = (QNum n)-translateB (Project (Var x, _) l, _) = QField x l-translateB (PrimApp "not" [arg], _) = QNot (translateB arg)-translateB (PrimApp "<" [l, r], _) = QOp (translateB l) Less (translateB r)-translateB b = error$ "translateB got unexpected term: " ++ (pretty.fst) b--compile :: TyEnv -> TypedTerm -> Query-compile env = translateTerm . normTerm env---- -- Tests---- -- FIXME: where does this belong? It tests a function internal to this--- -- module (normTerm) but uses testing apparatus that is defined at a--- -- "higher" layer (Narc.Test) and uses an otherwise unrelated module--- -- (Narc.Eval).--- prop_norm_sound :: TyEnv -> Env -> Property--- prop_norm_sound tyEnv env =--- forAll (sized (typeGen [])) $ \t ->--- forAll (sized (typedTermGen tyEnv t)) $ \m ->--- isErrorMSuccess $ tryErrorGensym $ --- do m' <- infer m--- return (eval env (normTerm tyEnv m') == eval env m')
− Narc/Contract.hs
@@ -1,7 +0,0 @@-module Narc.Contract where---- Contractual assertions ------------------------------------------------contract p x = if p x then x else error "Contract broken"--assert x e = if x then e else error "assertion failed"
− Narc/Debug.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--module Narc.Debug where--import Prelude hiding (catch)-import Control.Exception (catch, evaluate, throwIO, SomeException)-import Debug.Trace (trace)-import Foreign (unsafePerformIO)---- | Enable/disable debugging messages-debugFlag :: Bool-debugFlag = False---- | Trace the given string if debugging is on, or do nothing if not.-debug :: String -> a -> a-debug str = if debugFlag then trace str else id--breakFlag x = x -- a hook for a breakpoint in GHCi debugger---- | Force an arbitrary expression, tracing the @String@ arg if--- forcing produces an exception.-forceAndReport :: a -> String -> a-forceAndReport expr msg = - unsafePerformIO $- catch (evaluate $- expr `seq` expr- ) (\(exc::SomeException) ->- breakFlag $ - debug msg $ - Control.Exception.throwIO exc- )
− Narc/Eval.hs
@@ -1,119 +0,0 @@-module Narc.Eval where--import Narc.AST-import Narc.Debug (debug)-import Narc.Util (alistmap)------- Evaluation ----------------------------------------------------------------- { Values and value environments } -------------------------------------bind x v env = (x,v):env---- type RuntimeTerm = Term (Maybe Query)--type Env = [(Var, Value)]--data Value = VUnit | VBool Bool | VNum Integer- | VList [Value]- | VRecord [(String, Value)]- | VAbs Var TypedTerm Env- deriving (Eq, Show)--fromValue :: Value -> TypedTerm-fromValue VUnit = (Unit, undefined)-fromValue (VBool b) = (Bool b, undefined)-fromValue (VNum n) = (Num n, undefined)-fromValue (VList xs) = foldr1 union (map singleton $ map fromValue xs)- where union x y = (x `Union` y, undefined)- singleton x = (Singleton x, undefined)-fromValue (VRecord fields) = (Record (alistmap fromValue fields), undefined)-fromValue (VAbs x n env) = foldr (\(y,v) -> substTerm y (fromValue v))- (Abs x n, undefined) env--concatVLists xs = VList $ concat [x | (VList x)<-xs]--initialEnv :: Env-initialEnv =- []--- [("+",--- ((VAbs "x" (Abs "y"--- (PrimApp "+" [(Var "x", (TNum, openEpe), (Var "y", TNum)],--- Just (QOp (QVar "x") Plus (QVar "y"))), TNum) []),--- Just (QAbs "x" (QAbs "y" (QOp (QVar "x") Plus (QVar "y"))))))]---- | appPrim: apply a primitive function to a list of value arguments.-appPrim :: String -> [Value] -> Value-appPrim "+" [VNum a, VNum b] = VNum (a+b)-appPrim p _ = error("Unknown primitive" ++ p)---- | eval: Evaluate a typed term in a closing environment. Captures the--- effects performed by the term. (NB: type info is not actually used;--- should eliminate this.)-eval :: Env -> TypedTerm -> Value-eval env (Unit, _) = (VUnit)-eval env (Bool b, q) = (VBool b)-eval env (Num n, q) = (VNum n)-eval env (PrimApp prim args, q) = - let (vArgs) = map (eval env) args in- (appPrim prim vArgs)-eval env (Var x, q) =- case lookup x env of- Nothing -> error- ("Variable " ++ x ++ " not found in env " ++ show env ++ - " while evaluating term.")- Just v -> v-eval env (Abs x n, q) = (VAbs x n env')- where env' = filter (\(a,b) -> a `elem` fvs n) env-eval env (App l m, q) = - let (v) = eval env l in- let (w) = eval env m in- case v of- (VAbs x n env') -> - let env'' = bind x w env' in- let (r) = eval env'' n in- (r)- _ -> error "non-function applied"-eval env (Table name fields, q) = - (VList [])-eval env (If c a b, _) =- let (VBool t) = eval env c in- let (result) = if t then eval env a else eval env b in- (result)-eval env (Nil, _) =- (VList [])-eval env (Singleton body, q) =- let (v) = eval env body in- (VList [v])-eval env (Union m n, _) =- let (VList v) = eval env m in- let (VList w) = eval env n in- (VList $ v ++ w)-eval env (Record fields, q) =- let (vFields) = [let (value) = eval env term in- ((name, value))- | (name, term) <- fields] in- (VRecord vFields)-eval env (Project m f, q) =- let (v) = eval env m in- case v of- VRecord fields -> - case lookup f fields of {- Nothing -> error $ "No field " ++ f ++ " in " ++ - show v ++ "(" ++ show m ++ ")" ;- Just vField -> vField- }- _ -> error("Non-record value " ++ show v ++ " target of projection " ++ - show(Project m f))-eval env (Comp x src body, q) =- let (vSrc) = eval env src in- case vSrc of- (VList elems) -> - let (results) = [eval (bind x v env) body- | v <- elems] in- (concatVLists results)- _ -> error("Comprehension source was not a list.")--run = eval initialEnv
− Narc/Failure.hs
@@ -1,46 +0,0 @@-module Narc.Failure where--import Narc.Debug-import Control.Monad.Error hiding (when, join)-import Gensym---- Failure and ErrorGensym monads ----------------------------------------- (TBD: this is more general than Narc; factor it out.)--type Failure a = Either String a--fayl = Left -- TBD: would be better to make Failure a newtype & use- -- fail from Monad.---- instance Monad Failure where--- return = Failure . Right--- fail = Failure . Left--- x >>= k = case x of Failure(Left err) -> Failure(Left err)--- Failure(Right x') -> k x'--runError :: Either String t -> t-runError (Left e) = breakFlag $ error e-runError (Right x) = x--isError (Left x) = True-isError (Right _) = False--isSuccess (Left _) = False-isSuccess (Right _) = True--type ErrorGensym a = ErrorT String Gensym a---- | Run an ErrorGensym action, raising errors with `error'.-runErrorGensym = runError . runGensym . runErrorT---- | Try running an ErrorGensym action, packaging result in an Either--- | with Left as failure, Right as success.-tryErrorGensym = runGensym . runErrorT--under x = either throwError return x--isErrorMSuccess = either (const False) (const True) --instance Error () where- noMsg = ()- strMsg _ = ()
− Narc/Failure/QuickCheck.hs
@@ -1,17 +0,0 @@-module Narc.Failure.QuickCheck where--import Test.QuickCheck--import QCUtils-import Narc.Failure---- QuickCheck property utilities -----------------------------------------failureToProperty :: Test.QuickCheck.Testable a => Failure a -> Property-failureToProperty (Left _) = failProp-failureToProperty (Right x) = property x--failureToPropertyIgnoreFailure :: Test.QuickCheck.Testable a => - Failure a -> Property-failureToPropertyIgnoreFailure (Left _) = ignore-failureToPropertyIgnoreFailure (Right x) = property x
− Narc/HDBC.hs
@@ -1,13 +0,0 @@-module Narc.HDBC where--import Database.HDBC--import Narc.AST-import Narc.SQL-import Narc.Compile-import Narc.TypeInfer--run :: IConnection conn => Term a -> conn -> IO [[SqlValue]]-run t conn =- let sql = serialize (compile [] (runTyCheck [] t)) in- quickQuery conn sql []
− Narc/Pretty.hs
@@ -1,8 +0,0 @@-module Narc.Pretty where--import Narc.Common---- Pretty-printing ------------------------------------------------=====--class Pretty t where- pretty :: t -> String
− Narc/Rewrite.hs
@@ -1,67 +0,0 @@-module Narc.Rewrite where--import Data.Maybe (fromMaybe)--import Narc.AST-import Narc.Type-import Narc.Util (alistmap)---- Rewrite ------------------------------------------------------------------- Small-step version of compilation: local rewrite rules applied--- willy-nilly.--perhaps :: (a -> Maybe a) -> a -> a-perhaps f x = fromMaybe x (f x)--bu :: (Term a -> Maybe (Term a)) -> Term a -> Term a-bu f (Unit, d) = perhaps f (Unit, d)-bu f (Bool b, d) = perhaps f (Bool b, d)-bu f (Num n, d) = perhaps f (Num n, d)-bu f (Var x, d) = perhaps f (Var x, d)-bu f (Abs x n, d) = perhaps f (Abs x (bu f n), d)-bu f (App l m, d) = perhaps f (App (bu f l) (bu f m), d)-bu f (If c a b, d) =- perhaps f (If (bu f c)- (bu f a)- (bu f b), d)-bu f (Table tab fields, d) = perhaps f (Table tab fields, d)-bu f (Singleton m, d) = perhaps f (Singleton (bu f m), d)-bu f (Record fields, d) = perhaps f (Record (alistmap (bu f) fields), d)-bu f (Project m a, d) = perhaps f (Project (bu f m) a, d)-bu f (Comp x src body, d) = perhaps f (Comp x (bu f src) (bu f body), d)-bu f (PrimApp fun args, d) = perhaps f (PrimApp fun args, d)-bu f (Nil, d) = perhaps f (Nil, d)-bu f (Union a b, d) = perhaps f (Union a b, d)--rw (Comp x (Singleton m, _) n, t) = Just (substTerm x m n)-rw (App (Abs x n, st) m, t) = Just (substTerm x m n)-rw (Project (Record fields, rect) fld, t) = lookup fld fields-rw (Singleton (Var x, xT), t) = Nothing -- for now-rw (Comp x (Nil, _) n, t) = Just (Nil, t)-rw (Comp x m (Nil, _), t) = Just (Nil, t)-rw (Comp x (Comp y l m, s) n, t) = - if y `notElem` fvs n then- Just (Comp y l (Comp x m n, t), t)- else Nothing-rw (Comp x (m1 `Union` m2, s) n, t) =- Just ((Comp x m1 n, t) `Union` (Comp x m2 n, t), t)-rw (Comp x m (n1 `Union` n2, _), t) =- Just ((Comp x m n1, t) `Union` (Comp x m n2, t), t)-rw (Comp x (If b m (Nil, _), _) n, t) =- Just (Comp x m (If b n (Nil, t), t), t)-rw (If (b, bTy) m n, t@(TList _, _)) | fst n /= Nil =- Just((If (b,bTy) m (Nil, t), t) `Union`- (If (PrimApp "not" [(b, bTy)], bTy) n (Nil, t), t), t)-rw (If b (Nil, _) (Nil, _), t) = Just (Nil, t)-rw (If b (Comp x m n, _) (Nil, _), t) = Just (Comp x m (If b n (Nil, t), t), t) -rw (If b (m1 `Union` m2, _) (Nil, _), t) =- Just ((If b m1 (Nil, t), t) `Union` (If b m2 (Nil, t), t), t)--- push App inside If--- push Project inside If--- push If inside Record--- rw (IsEmpty m, t) --- | lorob t = Nothing--- | otherwise = --- IsEmpty (Comp "x" m (Singleton (Unit, TUnit), TList Tunit), TList TUnit)-rw _ = Nothing
− Narc/SQL.hs
@@ -1,155 +0,0 @@-module Narc.SQL where--import Data.List (nub, intercalate)--import Narc.Common-import Narc.Type-import Narc.Util (u, mapstrcat)------- SQL Queries --------------------------------------------------------------data Op = Eq | Less- | Plus | Minus | Times | Divide- deriving(Eq, Show)--data UnOp = Min | Max | Count | Sum | Average- deriving (Eq, Show)---- | Query: the type of SQL queries ("select R from Ts where B")--- (This is unpleasant; it should probably be organized into various--- syntactic classes.)-data Query = Select {rslt :: Query, -- make this a list- tabs :: [(Field, Field, Type)], -- use [(Field,Type)]- cond :: [Query]- }- | QNum Integer- | QBool Bool- | QNot Query- | QOp Query Op Query- | QField String String- | QRecord [(Field, Query)]- | QUnion Query Query- | QIf Query Query Query- | QExists Query- deriving(Eq, Show)--emptyQuery = Select {rslt = QRecord [], tabs = [], cond = [QBool False]}---- | @sizeQuery@ approximates the size of a query by calling giving up--- | its node count past a certain limit (currently limit = 100, below).-sizeQueryExact :: Query -> Integer-sizeQueryExact (q@(Select _ _ _)) =- sizeQueryExact (rslt q) + (sum $ map sizeQueryExact (cond q))-sizeQueryExact (QNum n) = 1-sizeQueryExact (QBool b) = 1-sizeQueryExact (QNot q) = 1 + sizeQueryExact q-sizeQueryExact (QOp a op b) = 1 + sizeQueryExact a + sizeQueryExact b-sizeQueryExact (QField t f) = 1-sizeQueryExact (QRecord fields) = sum [sizeQueryExact n | (a, n) <- fields]-sizeQueryExact (QUnion m n) = sizeQueryExact m + sizeQueryExact n-sizeQueryExact (QIf c a b) = sizeQueryExact c + sizeQueryExact a + sizeQueryExact b-sizeQueryExact (QExists q) = 1 + sizeQueryExact q---- | @sizeQuery@ approximates the size of a query by calling giving up--- | its node count past a certain limit (currently limit = 100, below).-sizeQuery :: Query -> Integer-sizeQuery qy = loop 0 qy- where- loop' :: Integer -> Query -> Integer- loop' n qy = if n > limit then n else loop n qy-- loop :: Integer -> Query -> Integer- loop n (q@(Select _ _ _)) = - let n' = foldr (\r n -> loop' n r) n (cond q) in- loop' n' (rslt q)- loop n (QNum i) = n + 1- loop n (QBool b) = n + 1- loop n (QNot q) = loop' (n+1) q- loop n (QOp a op b) = let n' = loop' (n+1) a in loop' n' b- loop n (QField t f) = n + 1- loop n (QRecord fields) = foldr (\r n -> loop' n r) n (map snd fields)- loop n (QUnion a b) = let n' = loop' (n+1) a in loop' n' b- loop n (QIf c a b) = - let n' = loop' (n+1) c in- let n'' = loop' n' a in- loop' n'' b- loop n (QExists q) = loop' (n+1) q-- limit = 100---- Basic functions on query expressions ----------------------------------freevarsQuery (q@(Select _ _ _)) = - (freevarsQuery (rslt q))- `u`- (nub $ concat $ map freevarsQuery (cond q))-freevarsQuery (QOp lhs op rhs) = nub (freevarsQuery lhs ++ freevarsQuery rhs)-freevarsQuery (QRecord fields) = concatMap (freevarsQuery . snd) fields-freevarsQuery _ = []--isQRecord (QRecord _) = True-isQRecord _ = False---- | a groundQuery is a *real* SQL query--one without variables or appl'ns.-groundQuery :: Query -> Bool-groundQuery (qry@(Select _ _ _)) =- all groundQueryExpr (cond qry) &&- groundQueryExpr (rslt qry) &&- isQRecord (rslt qry)-groundQuery (QUnion a b) = groundQuery a && groundQuery b-groundQuery (QExists qry) = groundQuery qry-groundQuery (QRecord fields) = all (groundQuery . snd) fields-groundQuery (QOp b1 _ b2) = groundQuery b1 && groundQuery b2-groundQuery (QNum _) = True-groundQuery (QBool _) = True-groundQuery (QField _ _) = True-groundQuery (QNot a) = groundQuery a---- | a groundQueryExpr is an atomic-type expression.-groundQueryExpr :: Query -> Bool-groundQueryExpr (qry@(Select _ _ _)) = False-groundQueryExpr (QUnion a b) = False-groundQueryExpr (QExists qry) = groundQuery qry-groundQueryExpr (QRecord fields) = all (groundQueryExpr . snd) fields-groundQueryExpr (QOp b1 _ b2) = groundQueryExpr b1 && groundQueryExpr b2-groundQueryExpr (QNot a) = groundQueryExpr a-groundQueryExpr (QNum _) = True-groundQueryExpr (QBool _) = True-groundQueryExpr (QField _ _) = True-groundQueryExpr (QIf c a b) = all groundQueryExpr [c,a,b]--serialize :: Query -> String-serialize q@(Select _ _ _) =- "select " ++ serializeRow (rslt q) ++- " from " ++ mapstrcat ", " (\(a, b, _) -> a ++ " as " ++ b) (tabs q) ++- " where " ++ if null (cond q) then- "true"- else mapstrcat " and " serializeAtom (cond q)-serialize (QUnion l r) =- "(" ++ serialize l ++ ") union (" ++ serialize r ++ ")"--serializeRow (QRecord flds) =- mapstrcat ", " (\(x, expr) -> serializeAtom expr ++ " as " ++ x) flds--serializeAtom (QNum i) = show i-serializeAtom (QBool b) = show b-serializeAtom (QNot expr) = "not(" ++ serializeAtom expr ++ ")"-serializeAtom (QOp l op r) = - serializeAtom l ++ " " ++ serializeOp op ++ " " ++ serializeAtom r-serializeAtom (QField rec fld) = rec ++ "." ++ fld-serializeAtom (QIf cond l r) = - "case when " ++ serializeAtom cond ++- " then " ++ serializeAtom l ++- " else " ++ serializeAtom r ++- " end)"-serializeAtom (QExists q) =- "exists (" ++ serialize q ++ ")"--serializeOp Eq = "="-serializeOp Less = "<"-serializeOp Plus = "<"-serializeOp Minus = "<"-serializeOp Times = "<"-serializeOp Divide = "<"
− Narc/SQL/Pretty.hs
@@ -1,39 +0,0 @@-module Narc.SQL.Pretty where--import Narc.Pretty-import Narc.SQL-import Narc.Util (mapstrcat)--instance Pretty Query where- pretty (Select{rslt=QRecord flds, tabs=tabs, cond=cond}) = - "select " ++ mapstrcat ", " (\(alias, expr) -> - pretty expr ++ " as " ++ alias)- flds ++ - (if null tabs then "" else- " from " ++ mapstrcat ", " (\(name, al, ty) -> name ++ " as " ++ al) - tabs) ++ - " where " ++ pretty_cond cond- where pretty_cond [] = "true"- pretty_cond cond = mapstrcat " and " pretty cond- pretty (QOp lhs op rhs) = pretty lhs ++ pretty op ++ pretty rhs- pretty (QRecord fields) = "{"++ mapstrcat ", "- (\(lbl,expr) -> - lbl ++ "=" ++ show expr) fields- ++ "}"- pretty (QNum n) = show n- pretty (QBool True) = "true"- pretty (QBool False) = "false"- - pretty (QField a b) = a ++ "." ++ b-- pretty (QUnion a b) = pretty a ++ " union all " ++ pretty b- pretty (QNot b) = "not " ++ pretty b- pretty (QIf c t f) = "if " ++ pretty c ++ " then " ++ pretty t- ++ " else " ++ pretty f---- Pretty-printing for Op, common to both AST and SQL languages.--instance Pretty Op where- pretty Plus = " + "- pretty Eq = " = "- pretty Less = " < "
− Narc/TermGen.hs
@@ -1,196 +0,0 @@-module Narc.TermGen where--import Control.Monad hiding (when)--import Test.QuickCheck hiding (promote, Failure)--import Gensym-import QCUtils--import Narc.AST-import Narc.SQL-import Narc.Type as Type-import Narc.Util------- QuickCheck term generators -----------------------------------------------smallIntGen :: Gen Int-smallIntGen = elements [0..5]--typeGen :: [TyVar] -> Int -> Gen Type-typeGen tyEnv size =- oneof $ [return TBool,- return TNum- ] ++- [do x <- elements tyEnv; return $ TVar x | length tyEnv > 0] ++- whens (size > 0)- [- do s <- typeGen tyEnv (size-1)- t <- typeGen tyEnv (size-1)- return $ TArr s t,- do t <- typeGen tyEnv (size-1)- return $ TList t,- do n <- smallIntGen :: Gen Int- fields <- sequence [do t <- typeGen tyEnv (size-1)- return ('f':show i, t) | i <- [0..n]]- return $ TRecord fields- ]---- | Generate a random term, unlikely to be well-typed.-termGen :: [Var] -> Int -> Gen (Term ())-termGen fvs size = frequency $- [(1, return (Unit, ())),- (1, do b <- arbitrary; return (Bool b, ())),- (1, do n <- arbitrary; return (Num n, ()))- ]- ++- (whens (not (null fvs)) [(3, do x <- elements fvs;- return (Var x, ()))])- ++- whens (size > 0) [- (3, do x <- varGen- n <- termGen (x:fvs) (size-1)- return (Abs x n, ())),- (6, do m <- termGen fvs (size-1)- n <- termGen fvs (size-1)- return $ (App m n, ())),- (6, do m <- termGen fvs (size-1)- f <- identGen- return $ (Project m f, ())),- (6, do m <- termGen fvs (size-1)- return $ (Singleton m, ())),- (18, do n <- smallIntGen- tableName <- identGen- fields <- sequence $ replicate n $- do name <- identGen- ty <- elements [TBool, TNum]- return (name, ty)- return $ (Table tableName fields, ())),- (9, do n <- smallIntGen- fields <- sequence $ replicate n $- do name <- identGen- term <- termGen fvs (size-1)- return (name, term)- return $ (Record fields, ())),- (72, do x <- varGen -- Overwhelmingly favor comprehensions when- -- we have enough size remaining, since- -- we'll be favoring other stuff when we run- -- out of size.- l <- termGen fvs (size-1)- m <- termGen (x:fvs) (size-1)- return $ (Comp x l m, ()))- ]--closedTermGen :: Int -> Gen (Term' (), ())-closedTermGen size = - termGen [] size--oneofMaybe :: [Gen(Maybe a)] -> Gen (Maybe a)-oneofMaybe [] = return Nothing-oneofMaybe (x:xs) = do x' <- x- xs' <- oneofMaybe xs- case (x', xs') of- (Nothing, Nothing) -> return Nothing- _ -> oneof (map (return . Just) $ - asList x' ++ asList xs')---- Why isn't this bloody thing generating deconstructors??-typedTermGen :: TyEnv -> Type -> Int -> Gen (Term ())-typedTermGen env ty sz = --- debug ("generating term (type " ++ show ty ++ ") at size " ++ show sz) $- frequency (- -- variables- -- (NOTE: presently only gens vars that have ground type, sans quant'rs)- [(2, return $ (Var x, ())) | (x, (xQs, xTy)) <- env,- xQs == [] && xTy == ty] ++- -- constructors- (case ty of- TNum -> [(1, do n <- arbitrary; return (Num n, ()))]- TBool -> [(1, do b <- arbitrary; return (Bool b, ()))]- TArr s t -> - [(2, do x <- varGen - n <- typedTermGen ((x, ([], s)):(unassoc x env)) t decSz- return $ (Abs x n, ()))]- TRecord fTys -> - [(2, do fields <- forM fTys $ \(lbl, ty) ->- do m <- typedTermGen env ty decSz- return (lbl, m)- return $ (Record fields, ()))]- TList ty ->- [(2, do m <- typedTermGen env ty decSz - return $ (Singleton m, ()))]- ++ case ty of - TRecord fTys ->- if not (and [isBaseTy ty | (_, ty) <- fTys]) then [] else- [(2, do tab <- identGen- return $ (Table ('T':tab) fTys, ()))]- _ -> []- _ -> error("Strange type while generating term: " ++ - show ty ++ " (size " ++ show sz ++ ")")- ) ++- -- deconstructors- if (sz <= 0) then [] else (- [- (10, do s <- typeGen [] (intSqrt sz)- m <- typedTermGen env (TArr s ty) decSz- n <- typedTermGen env s decSz- return $ (App m n, ())),- (10, do c <- typedTermGen env TBool decSz- a <- typedTermGen env ty decSz- b <- typedTermGen env ty decSz- return $ (If c a b, ()))- ] ++- -- Comprehension: a constructor and a destructor- case ty of- (TList _) ->- [(20, do x <- varGen- s <- typeGen [] (intSqrt sz)- src <- typedTermGen env (TList s) decSz- let env' = Type.bind x ([], s) env- body <- typedTermGen env' ty decSz- return (Comp x src body, ()))- ]- _ -> []- )- )- where decSz = max (sz-1) 0--closedTypedTermGen :: Type -> Int -> Gen (Term ())-closedTypedTermGen ty size = --- let tyEnv = runErrorGensym makeInitialTyEnv in- let tyEnv = [] in- typedTermGen tyEnv ty size--dbTableTypeGen :: Gen Type-dbTableTypeGen = - do n <- nonNegInt :: Gen Int- ty <- elements [TBool, TNum]- return $ TList (TRecord [('t': show i, ty) | i <- [0..n-1]])----- Generators--instance Arbitrary Op where- arbitrary = oneof [return Eq, return Less]--listGen :: Int -> Gen a -> Gen [a]-listGen 0 gen = oneof [ return [], do x <- gen- xs <- listGen 0 gen- return (x : xs)]-listGen n gen = do x <- gen- xs <- listGen (n-1) gen- return (x : xs)--identCharGen :: Gen Char-identCharGen = oneof $ map return (['a'..'z'] ++ ['A'..'Z'] ++ ['_'])--identGen :: Gen String-identGen = listGen 1 identCharGen--varGen :: Gen String-varGen = (return ('x':)) `ap` identGen--pairGen :: Gen a -> Gen b -> Gen (a, b)-pairGen aGen bGen = do a <- aGen; b <- bGen; return (a, b)
− Narc/Test.hs
@@ -1,82 +0,0 @@-{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}--module Narc.Test where--import Prelude hiding (catch)-import Control.Monad.State hiding (when, join)-import Control.Monad.Error ({- Error(..), throwError, -} runErrorT)--import Test.QuickCheck hiding (promote, Failure)-import Test.HUnit hiding (State, assert)--import Gensym-import QCUtils--import Narc.AST-import Narc.Compile-import Narc.Failure-import Narc.SQL-import Narc.Type as Type-import Narc.TypeInfer-import Narc.TermGen--makeNormalizerTests :: ErrorGensym Test-makeNormalizerTests = - do initialTyEnv <- makeInitialTyEnv - return$ TestList - [TestCase $ unitAssert $ - let term = (Comp "x" (Table "foo" [("fop", TNum)], ())- (If (Bool True,())- (Singleton (Record- [("f0", (Project (Var "x", ())- "fop",()))],()),())- (Singleton (Record - [("f0", (Num 3, ()))], ()), ()), - ()), ()) in- let tyTerm = runErrorGensym $ infer $ term in- groundQuery $ compile initialTyEnv $ tyTerm- ]--unitTests :: ErrorGensym Test-unitTests = do normalizerTests <- makeNormalizerTests - return $ TestList [tyCheckTests, normalizerTests, typingTest]--runUnitTests :: IO Counts-runUnitTests = runErrorGensym $ liftM runTestTT unitTests------- Big QuickCheck properties------- | Assertion that well-typed terms evaluate without throwing.-prop_eval_safe :: Property-prop_eval_safe = - forAll dbTableTypeGen $ \ty ->- forAll (sized (closedTypedTermGen ty)) $ \m ->- case tryErrorGensym (infer m) of- Left _ -> label "ill-typed" $ property True -- ignore ill-typed terms- -- but report their occurence.- Right (m'@(_, ty)) -> - isDBTableTy ty ==>- let q = (compile [] $! m') in- collect (sizeQuery q) $ -- NB: Counts sizes only up to ~100.- excAsFalse (q == q) -- Self-comparison forces the- -- value (?) thus surfacing- -- any @error@s that might be- -- raised.--prop_typedTermGen_tyCheck :: Property-prop_typedTermGen_tyCheck =- forAll (sized $ typeGen []) $ \ty ->- forAll (sized $ typedTermGen (runErrorGensym makeInitialTyEnv) ty) $ \m ->- case runGensym $ runErrorT $ infer m of- Left _ -> False- Right (_m', ty') -> isErrorMSuccess $ unify ty ty'---- Main ------------------------------------------------------------------main :: IO ()-main = do- quickCheckWith tinyArgs prop_eval_safe- _ <- runUnitTests- return ()
− Narc/Type.hs
@@ -1,245 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--module Narc.Type where--import Test.QuickCheck--import Gensym-import QCUtils--import Data.List ((\\))-import Control.Monad.State (State(..), get, put, evalState) -- TBD: use Gensym monad instead-import Control.Applicative ((<$>))-import Narc.Failure (Failure, fayl)-import Narc.Failure.QuickCheck-import Narc.Util (dom, rng, image, alistmap, sortAlist, onCorresponding,- disjointAlist, validEnv, eqUpTo)-import Narc.Var--type TyVar = Int--data Type = TBool | TNum | TString | TUnit | TList Type- | TArr Type Type- | TRecord [(String, Type)]- | TVar TyVar- deriving (Eq, Show)--type QType = ([TyVar], Type)--type TySubst = [(Int, Type)]--type TyEnv = [(Var, QType)]---- Operations on types, rows and substitutions --------------------------isBaseTy TBool = True-isBaseTy TNum = True-isBaseTy TString = True-isBaseTy _ = False--isTyVar (TVar _) = True-isTyVar _ = False--isDBRecordTy (TRecord fields) = all (isBaseTy . snd) fields-isDBRecordTy _ = False--isRecordTy (TRecord fields) = True-isRecordTy _ = False--isDBTableTy (TList ty) = isDBRecordTy ty-isDBTableTy _ = False--emptyTySubst :: (TySubst)-emptyTySubst = ([])---- | ftvs: free type variables-ftvs TBool = []-ftvs TNum = []-ftvs TString = []-ftvs TUnit = []-ftvs (TList t) = ftvs t-ftvs (TArr s t) = ftvs s ++ ftvs t-ftvs (TRecord fields) = concat [ftvs t | (lbl, t) <- fields]-ftvs (TVar a) = [a]--numFtvs = length . ftvs---- | ftvsSubst: the free type variables of a type substitution--that is,--- the type variables free in the types in the range of the substitution.-ftvsSubst a = concatMap ftvs $ rng a---- | occurs x ty: does variable x appear in type ty? (Note there are no--- type-variable binders).-occurs x (TVar y) | x == y = True- | otherwise = False-occurs x (TArr s t) = x `occurs` s || x `occurs` t-occurs x (TList t) = x `occurs` t-occurs x (TRecord t) = any (occurs x) (map snd t)-occurs x (TUnit) = False-occurs x (TBool) = False-occurs x (TNum) = False-occurs x (TString) = False--applyTySubst :: TySubst -> Type -> Type-applyTySubst subst (TUnit) = TUnit-applyTySubst subst (TBool) = TBool-applyTySubst subst (TNum) = TNum-applyTySubst subst (TString) = TString-applyTySubst subst (TVar a) = case lookup a subst of- Nothing -> TVar a- Just ty -> ty-applyTySubst subst (TArr a b) =- TArr (applyTySubst subst a) (applyTySubst subst b)-applyTySubst subst (TList a) = TList (applyTySubst subst a)-applyTySubst subst (TRecord a) = TRecord (alistmap (applyTySubst subst) a)----- Type operations -------------------------------------------------------instantiate (qs, ty) =- do subst <- sequence [do y <- gensym ; return (q, TVar y) | q <- qs]- return $ applyTySubst subst ty--{- | normalizeType:- Renumber all the type variables in a normal way to allow- comparing types.--}-normalizeType :: Type -> State (Int, [(Int, Int)]) Type-normalizeType TBool = return TBool-normalizeType TNum = return TNum-normalizeType TString = return TString-normalizeType TUnit = return TUnit-normalizeType (TList ty) = TList <$> normalizeType ty-normalizeType (TRecord recTy) = undefined-normalizeType (TVar a) = do (ctr, env) <- Control.Monad.State.get- case lookup a env of- Nothing -> do put (ctr+1, (a, ctr):env)- return $ TVar ctr- Just b -> return $ TVar b-normalizeType (TArr s t) =- do s' <- normalizeType s- t' <- normalizeType t- return $ TArr s' t'--runNormalizeType ty = evalState (normalizeType ty) (0, [])---- instanceOf: is there a substitution that turns ty2 into ty1? Useful in tests-instanceOf :: Type -> Type -> Failure ()-instanceOf ty1 (TVar x) = return ()-instanceOf TBool TBool = return ()-instanceOf TNum TNum = return ()-instanceOf TString TString = return ()-instanceOf (TArr s t) (TArr u v) = - instanceOf t v >>- instanceOf s u-instanceOf (TList a) (TList b) = instanceOf a b-instanceOf (TRecord a) (TRecord b) = - let a' = sortAlist a - b' = sortAlist b- in- do result <- sequence [if ax == bx then instanceOf ay by else fayl "Record mismatch"- | ((ax, ay), (bx, by)) <- zip a' b']- return ()-instanceOf a b = fayl ""--unify :: Type -> Type -> Failure (TySubst)-unify s t | s == t = return ([])-unify (TVar x) t | not (x `occurs` t) = return ([(x, t)])- | otherwise = fayl("Occurs check failed on " ++ - show (TVar x) ++ " and " ++ show t)-unify t (TVar x) | not (x `occurs` t) = return ([(x, t)])- | otherwise = fayl("Occurs check failed on " ++ - show t ++ " and " ++ show (TVar x))-unify TBool TBool = return ([])-unify TNum TNum = return ([])-unify TString TString = return ([])-unify (TArr s t) (TArr u v) = - do substSU <- unify s u- substTV <- unify (applyTySubst substSU t)- (applyTySubst substSU v)- composeTySubst [substTV, substSU]-unify (TList a) (TList b) = unify a b-unify (TRecord a) (TRecord b) = - let a' = sortAlist a - b' = sortAlist b- in- do substs <- sequence- [if ax == bx then unify ay by else- fayl ("Record types " ++ show a' ++ - " and " ++ show b' ++ - " mismatched.")- | ((ax, ay), (bx, by)) <- zip a' b']- let (tySubsts) = substs- subst <- composeTySubst tySubsts- return (subst)-unify a b = fayl("Type mismatch between " ++ - show a ++ " and " ++ show b)--unifyAll :: [Type] -> Failure TySubst-unifyAll [] = return ([])-unifyAll [x] = return ([])-unifyAll (x1:x2:xs) = do (tySubst) <- x1 `unify` x2- unifyAll (map (applyTySubst tySubst)- (x2:xs))--composeTySubst :: [TySubst] -> Failure TySubst-composeTySubst [] = return $ ([])-composeTySubst subst =- let (tySubsts) = subst in- do addlSubsts <- sequence $- onCorresponding unifyAll $ concat tySubsts- let (addlTySubsts) = addlSubsts- let substs' = tySubsts ++ addlTySubsts- let tySubst = flip foldr1 substs'- (\s1 s2 -> s1 ++ alistmap (applyTySubst s1) s2)- if any (\(a,b) -> occurs a b) tySubst then - fayl "Circular type substitution in composeTySubst" - else - return (tySubst)--prop_composeTySubst = - forAll (genEnv 0) $ \a ->- forAll (genEnv (length a)) $ \b ->- forAll arbitrary $ \ty ->- disjointAlist a b && validEnv a && validEnv b ==>- (do ab <- composeTySubst[a, b]- return $ applyTySubst ab ty)- == (return $ (applyTySubst a $ applyTySubst b ty) :: Failure Type)---- unused-disjoinSubst :: TySubst -> TySubst -> TySubst-disjoinSubst a b =- [(image bx mapping, applyTySubst subst by) | (bx, by) <- b]- where mapping = (dom b ++ ftvsSubst b) `zip`- ([0..] \\ (dom a ++ ftvsSubst a))- subst = alistmap TVar mapping--instance Arbitrary Type where- arbitrary = - oneof- [ return TBool- , return TNum- , return TString- , do s <- arbitrary- t <- arbitrary- return (TArr s t)- , do x <- posInt- return (TVar x)- ]---- Check that unification produces a substitution which actually--- unifies the two types. (Currently fails; something wrong with the way--- substitutions are composed or not.)-prop_unify_apply_subst = - forAll arbitrary $ \(a :: Type) ->- forAll arbitrary $ \(b :: Type) -> - collect (numFtvs a, numFtvs b) $- failureToPropertyIgnoreFailure $- do (subst) <- a `unify` b- let e = applyTySubst subst a- let f = applyTySubst subst b- return $ eqUpTo runNormalizeType e f---- { Typing environments } -----------------------------------------------bind x v env = (x,v):env
− Narc/TypeInfer.hs
@@ -1,233 +0,0 @@-module Narc.TypeInfer where--import Data.Maybe (fromMaybe)-import Data.Either--import Control.Monad.State (lift)--import Test.HUnit--import Gensym-import Narc.AST-import Narc.Type-import Narc.Failure-import Narc.Debug (debug)------- Type inference -----------------------------------------------------------tyCheckTerms env terms = - do results <- sequence [tyCheck env term | term <- terms]- let (tySubsts, terms') = unzip results- let (terms'', termTys) = unzip terms'- tySubst <- under $ composeTySubst tySubsts- return (tySubst, terms')---- | tyCheck env term infers a type for term in environment env.--- The environment has type [(Var, QType)];--- an entry (x, qty) indicates that variable x has the quantified type qty;--- a QType (ys, ty) indicates the type "forall ys, ty".-tyCheck :: TyEnv -> Term a- -> ErrorGensym (TySubst, Term Type)-tyCheck env (Unit, _) = - do let ty = (TUnit)- return (emptyTySubst, (Unit, ty))-tyCheck env (Bool b, _) = - do let ty = (TBool)- return (emptyTySubst, (Bool b, ty))-tyCheck env (Num n, _) = - do let ty = (TNum)- return (emptyTySubst, (Num n, ty))-tyCheck env (String s, _) = - do let ty = (TString)- return (emptyTySubst, (String s, ty))-tyCheck env (Table t tys, _) =- do let ty = (TList (TRecord tys))- return (emptyTySubst, (Table t tys, ty))-tyCheck env (Var x, _) =- do let qTy = fromMaybe (error("Type error: no var " ++ x))- $ lookup x env- ty <- lift $ instantiate qTy- debug ("*** instantiated " ++ show qTy ++ " to " ++ show ty) $- return (emptyTySubst, (Var x, (ty)))-tyCheck env (PrimApp fun args, _) = - do (tySubst, args) <- tyCheckTerms env args- return(tySubst, (PrimApp fun args, (TBool))) -- TBD-tyCheck env (Abs x n, _) = - do argTyVar <- lift gensym- (tySubst, n'@(_, (nTy))) <- - tyCheck ((x, ([], TVar argTyVar)) : env) n- let argTy = applyTySubst tySubst $ TVar argTyVar- return(tySubst,- (Abs x n', (TArr argTy nTy)))-tyCheck env (If c a b, _) =- do (cTySubst, c'@(_, (cTy))) <- tyCheck env c- (aTySubst, a'@(_, (aTy))) <- tyCheck env a- (bTySubst, b'@(_, (bTy))) <- tyCheck env b- cBaseTySubst <- under (unify cTy TBool)- abTySubst <- under $ unify aTy bTy- tySubst <- under $ composeTySubst- [aTySubst, bTySubst, cTySubst,- cBaseTySubst, abTySubst]- let ty = applyTySubst tySubst bTy- return (tySubst,- (If c' a' b', (ty)))-tyCheck env (Nil, _) = - do t <- lift gensym- return (emptyTySubst, (Nil, (TList (TVar t))))-tyCheck env (Singleton m, _) =- do (tySubst, m'@(_, (mTy))) <- tyCheck env m- return (tySubst,- (Singleton m', (TList mTy)))-tyCheck env (Union a b, _) =- do (aTySubst, a'@(_, (aTy))) <- tyCheck env a- (bTySubst, b'@(_, (bTy))) <- tyCheck env b- abTySubst <- under $ unify aTy bTy- tySubst <- under $ composeTySubst- [aTySubst, bTySubst, abTySubst]- let ty = applyTySubst tySubst bTy- return (tySubst,- (Union a' b', (ty)))-tyCheck env (Record fields, _) =- let (fieldNames, terms) = unzip fields in- do (tySubst, terms) <- tyCheckTerms env terms- let fieldTys = map snd terms- return (tySubst,- (Record (zip fieldNames terms),- (TRecord [(name,ty)| (ty, name) <- zip fieldTys fieldNames])))-tyCheck env (Project m f, _) =- do rowVar <- lift gensym; a <- lift gensym- (tySubst, m'@(_, mTy)) <- tyCheck env m- case mTy of- TVar x -> -- Note: bogus- return (((x, TRecord [(f, TVar a)]):tySubst),- (Project m' f, (TVar a)))- TRecord fieldTys ->- case lookup f fieldTys of- Nothing -> fail("no field " ++ f ++ " in record " ++ - show (strip m))- Just fieldTy ->- return (tySubst,- (Project m' f, fieldTy))- _ -> fail("Project from non-record type.")-tyCheck env (App m n, _) = - do a <- lift gensym; b <- lift gensym;- (mTySubst, m'@(_, (mTy))) <- tyCheck env m- (nTySubst, n'@(_, (nTy))) <- tyCheck env n- let nTy' = applyTySubst mTySubst $ nTy- let mTy' = applyTySubst nTySubst $ mTy- subExprTySubst <- under $ composeTySubst [mTySubst, nTySubst]- - let mArrTy = TArr (nTy') (TVar b)- mArrTySubst <- under $ unify mArrTy mTy'- - let resultTy = applyTySubst mArrTySubst $ TVar b- - tySubst <- under $ composeTySubst [mArrTySubst,- subExprTySubst]- - return (tySubst,- (App m' n', (resultTy)))--tyCheck env term@(Comp x src body, d) =- do (substSrc, src') <- tyCheck env src- let srcTy = typeAnno src'- a <- lift gensym- srcTySubst <- under $ unify (TList (TVar a)) srcTy- let srcTy' = applyTySubst srcTySubst (TVar a)- (substBody, body') <- tyCheck ((x, unquantType srcTy') : env) body- let bodyTy = typeAnno body'- resultSubst <- under $ composeTySubst [substSrc, substBody]- return (resultSubst, (Comp x src' body', bodyTy))--unquantType ty = ([], ty)--typeAnno :: Term Type -> Type-typeAnno (_, ty) = ty--makeInitialTyEnv :: ErrorGensym [(String, QType)]-makeInitialTyEnv = return []--infer :: Term a -> ErrorGensym TypedTerm -- FIXME broken, discards subst'n-infer term =- do initialTyEnv <- makeInitialTyEnv- (_, term') <-- -- runErrorGensym $ - tyCheck initialTyEnv term- return term'--infer' :: Term' a -> ErrorGensym TypedTerm-infer' term = infer (term, undefined)--runInfer = runErrorGensym . infer--runTyCheck env m = runErrorGensym $ - do initialTyEnv <- makeInitialTyEnv- (subst, m') <- tyCheck (initialTyEnv++env) m- return $ retagulate (applyTySubst subst . snd) m'--inferTys :: Term () -> ErrorGensym Type-inferTys m = - do (_, (ty)) <- infer m- return (ty)--inferType :: Term () -> ErrorGensym Type-inferType m = infer m >>= (return . snd)--runInferType = runErrorGensym . inferType--inferType' :: Term' () -> ErrorGensym Type-inferType' m = infer' m >>= (return . snd)---- UNIT TESTS ------------------------------------------------------------unitAssert b = assertEqual "." b True--tyCheckTests =- TestList ["Simple application of id to table" ~:- (runErrorGensym $ - inferTys (App (Abs "x" (Var "x", ()), ())- (Table "wine" [], ()), ()))- ~?= (TList (TRecord [])),- "Curried application of id to table" ~:- (runErrorGensym . inferTys)- (App (App- (Abs "x" (Abs "y" (App (Var "x", ())- (Var "y", ()), ()), ()), ())- (Abs "x" (Var "x", ()), ()), ())- (Table "wine" [], ()), ())- ~?= (TList (TRecord [])),- "Curried application, de/reconstructing record" ~:- (runErrorGensym . inferTys) - (App (App- (Abs "f" (Abs "x" (App (Var "f",()) (Var "x",()),()),()),())- (Abs "x"- (Record[("baz", (Project(Var "x",()) "foo", ()))],- ()),- ()), ())- (Record [("foo", (Num 47, ()))], ()), ())- ~?= (TRecord[("baz", TNum)]),- "omega" ~:- unitAssert $ isError $- (tryErrorGensym . inferType)- (Abs "x" (App (Var "x", ()) (Var "x", ()), ()), ())- ]--typingTest1 = - let idTy = (TArr (TVar 9) (TVar 9)) in- let concatMapTy = (TArr (TArr (TVar 2) (TList (TVar 3)))- (TArr (TList (TVar 2))- (TList (TVar 3)))) in- let Right mArrSubst = unify concatMapTy (TArr (TVar 4) (TVar 5)) in- let argTy = applyTySubst mArrSubst (TVar 4) in- -- TArr (TVar 2) ([],Just 0) (TList (TVar 3))- let Right funcArgSubst = unify argTy idTy in- let resultTy = (applyTySubst funcArgSubst $ applyTySubst mArrSubst (TVar 5)) - in- (resultTy, funcArgSubst,- case resultTy of- TArr (TList (TList (TVar a))) (TList (TVar b)) -> a == b)--typingTest = let (_,_,x) = typingTest1 in - TestCase (unitAssert x)
− Narc/Util.hs
@@ -1,111 +0,0 @@-module Narc.Util where--import Data.Maybe (fromJust, isJust)-import Data.List as List ((\\), nub, intersperse, groupBy, sortBy, sort)------- List Utilities-----dom alist = map fst alist-rng alist = map snd alist--collate proj = groupBy (\x y -> proj x == proj y) . - sortBy (\x y -> proj x `compare` proj y)--sortAlist :: [(String, b)] -> [(String, b)]-sortAlist = sortBy (\a b -> fst a `compare` fst b)--cross f g (x,y) = (f x, g y)--onRight f = cross id f-onLeft f = cross f id---- | shadow: given two alists, return the elements of the first that--- are NOT mapped by the second-shadow as bs = [(a,x) | (a,x) <- as, a `notElem` domBs]- where domBs = map fst bs---- | Tests that an alist or environment is well-formed: that its first--- | components are all unique.-validEnv xs = length (nub $ map fst xs) == length xs--mr agg xs = map reduceGroup (collate fst xs)- where reduceGroup xs = let (as, bs) = unzip xs in- (the as, agg bs)- the xs | allEq xs = head xs--onCorresponding :: Ord a => ([b]->c) -> [(a,b)] -> [c]-onCorresponding agg xs = map reduceGroup (collate fst xs)- where reduceGroup xs = agg $ map snd xs--($>) x f = f x--image x = fromJust . lookup x--maps x = isJust . lookup x--intSqrt :: Integral a => a -> a-intSqrt = floor . sqrt . fromIntegral--unassoc a = filter ((/= a) . fst)--nubassoc [] = []-nubassoc ((x,y):xys) = (x,y) : (nubassoc $ unassoc x xys)--graph f xs = [(x, f x) | x <- xs]-alistmap f = map (\(a, b) -> (a, f b))--bagEq a b = a \\ b == [] && b \\ a == []--setEq a b = (nub a) `bagEq` (nub b)--u a b = nub (a ++ b)--contains a b = null(b \\ a)--setMinus xs ys = nub $ sort $ xs \\ ys--(\\\) xs ys = setMinus xs ys--allEq [] = True-allEq (x:xs) = all (== x) xs--disjoint :: Eq a => [a] -> [a] -> Bool-disjoint xs ys = not (any (\x-> any (==x) ys) xs)--disjointAlist xs ys = disjoint (map fst xs) (map fst ys)--- disjointAlist [] _ = True--- disjointAlist _ [] = True--- disjointAlist xs ((a,b):ys) =--- (not $ any ((== a) . fst) xs) && disjointAlist xs ys---- | Convert a maybe to a zero-or-one-element list.-asList :: Maybe a -> [a]-asList Nothing = []-asList (Just x) = [x]--isRight :: Either a b -> Bool-isRight (Right _) = True-isRight (Left _ ) = False--isLeft :: Either a b -> Bool-isLeft (Left _) = True-isLeft (Right _ ) = False---- | zipAlist: given two alists with the same domain,--- returns an alist mapping each of those domain values to--- the pair of the two corresponding values from the given lists.-zipAlist xs ys = - let xsys = zip (sortAlist xs) (sortAlist ys) in- if not $ and [x == y | ((x, a), (y, b)) <- xsys] then - error "alist mismatch in zipAlist"- else [(x, (a, b)) | ((x, a), (y, b)) <- xsys]---- | mapstrcat: transform a list to one of strings, with a given--- | function, and join these together with some `glue' string.-mapstrcat :: String -> (a -> String) -> [a] -> String-mapstrcat glue f xs = concat $ List.intersperse glue (map f xs)---- Functional utilities-eqUpTo f x y = f x == f y
− Narc/Var.hs
@@ -1,4 +0,0 @@-module Narc.Var where--type Var = String-
narc.cabal view
@@ -7,13 +7,28 @@ -- The package version. See the Haskell package versioning policy -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for -- standards guiding when and how versions should be incremented.-Version: 0.1+Version: 0.1.1 -- A short (one-line) description of the package. Synopsis: Query SQL databases using Nested Relational Calculus embedded in Haskell. -- A longer description of the package.--- Description: +Description: Narc is an embedded language for querying SQL databases, + which permits using the "nested relational" model, a more+ flexible model than the traditional relational model of SQL+ databases. In spite of this richer data model, queries are+ transformed into SQL to run against standard databases.+ .+ To use the language, express a query using the combinators+ like @foreach@, @table@, @having@, @singleton@ and so on.+ Wrap this in a call to @narcToSQLString@ to get a string+ that can be sent to a SQL database.+ .+ To send a query directly to an HDBC connection, import+ the module @Database.Narc.HDBC@ and use its @run@ function,+ passing it an HDBC connection and a Narc query. The result+ is an IO action that returns a 2-D list of result values+ in HDBC's usual format. -- URL for the project homepage or repository. Homepage: http://ezrakilty.net/projects/narc@@ -48,13 +63,13 @@ Library -- Modules exported by the library.- Exposed-modules: Narc, Narc.AST, Narc.Common, Narc.Compile, Narc.Eval, Narc.Failure, Narc.Pretty, Narc.Rewrite, Narc.SQL, Narc.Test, Narc.Type, Narc.TypeInfer, Narc.Util, Narc.AST.Pretty, Narc.Failure.QuickCheck, Narc.SQL.Pretty, Narc.HDBC+ Exposed-modules: Database.Narc, Database.Narc.SQL, Database.Narc.Test, Database.Narc.Type, Database.Narc.HDBC -- Packages needed in order to build this package.- Build-depends: base >=4 && < 5, HUnit, QuickCheck, mtl, random, HDBC+ Build-depends: base >=4 && < 4, HUnit, QuickCheck, mtl, random, HDBC -- Modules not exported by this package.- Other-modules: Gensym, QCUtils, Narc.TermGen, Narc.Var, Narc.Contract, Narc.Debug+ Other-modules: Gensym, QCUtils, Database.Narc.TermGen, Database.Narc.Var, Database.Narc.Contract, Database.Narc.Debug, Database.Narc.TypeInfer, Database.Narc.Util, Database.Narc.AST.Pretty, Database.Narc.Failure.QuickCheck, Database.Narc.Rewrite, Database.Narc.AST, Database.Narc.Common, Database.Narc.Compile, Database.Narc.Eval, Database.Narc.Failure, Database.Narc.Pretty, Database.Narc.SQL.Pretty -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source. -- Build-tools: