Translating linearization functions to Haskell: support for variants

By adding the flag -haskell=variants to the command line, GF will now generate
linearization functions in Haskell that support variants. Variants are
represented as lists in Haskell.

Variants inside pre { ... } expressions are still ignored.

TODO: apply some monad laws to generate more compact code (using an
intermediate representation of the generated Haskell code, instead of
pretty printing directly from the GF code).
This commit is contained in:
hallgren
2015-02-09 16:24:33 +00:00
parent 3509ee650d
commit 8e4e8da105
3 changed files with 91 additions and 49 deletions

View File

@@ -8,7 +8,7 @@ import GF.Data.Utilities(mapSnd)
import GF.Text.Pretty import GF.Text.Pretty
import GF.Grammar.Grammar import GF.Grammar.Grammar
import GF.Grammar.Lookup(lookupFunType,lookupOrigInfo,allOrigInfos)--,allParamValues import GF.Grammar.Lookup(lookupFunType,lookupOrigInfo,allOrigInfos)--,allParamValues
import GF.Grammar.Macros(typeForm,collectOp,mkAbs,mkApp) import GF.Grammar.Macros(typeForm,collectOp,collectPattOp,mkAbs,mkApp)
import GF.Grammar.Lockfield(isLockLabel) import GF.Grammar.Lockfield(isLockLabel)
import GF.Grammar.Predef(cPredef,cInts) import GF.Grammar.Predef(cPredef,cInts)
import GF.Compile.Compute.Predef(predef) import GF.Compile.Compute.Predef(predef)
@@ -28,7 +28,7 @@ concretes2haskell opts absname gr =
concrete2haskell opts gr cenv absname cnc modinfo = concrete2haskell opts gr cenv absname cnc modinfo =
render $ render $
haskPreamble absname cnc $$ "" $$ haskPreamble va absname cnc $$ "" $$
"--- Parameter types ---" $$ "--- Parameter types ---" $$
vcat (neededParamTypes S.empty (params defs)) $$ "" $$ vcat (neededParamTypes S.empty (params defs)) $$ "" $$
"--- Type signatures for linearization functions ---" $$ "--- Type signatures for linearization functions ---" $$
@@ -54,7 +54,9 @@ concrete2haskell opts gr cenv absname cnc modinfo =
M.toList $ M.toList $
jments modinfo jments modinfo
signature c = "lin"<>c<+>"::"<+>"A."<>gId c<+>"->"<+>"Lin"<>c -- signature c = "lin"<>c<+>"::"<+>"A."<>gId c<+>"->"<+>"Lin"<>c
-- signature c = "--lin"<>c<+>":: (Applicative f,Monad f) =>"<+>"A."<>gId c<+>"->"<+>"f Lin"<>c
signature c = "lin"<>c<+>"::"<+>"A."<>gId c<+>"->"<+>pure ("Lin"<>c)
emptydefs = map emptydef (S.toList emptyCats) emptydefs = map emptydef (S.toList emptyCats)
emptydef c = "lin"<>c<+>"_"<+>"="<+>"undefined" emptydef c = "lin"<>c<+>"_"<+>"="<+>"undefined"
@@ -67,38 +69,44 @@ concrete2haskell opts gr cenv absname cnc modinfo =
params1 (Nothing,(_,rhs)) = paramTypes gr rhs params1 (Nothing,(_,rhs)) = paramTypes gr rhs
params1 (_,(_,rhs)) = tableTypes gr [rhs] params1 (_,(_,rhs)) = tableTypes gr [rhs]
ppDef (Nothing,(lhs,rhs)) = hang (lhs<+>"=") 4 (convType gId rhs) ppDef (Nothing,(lhs,rhs)) = hang (lhs<+>"=") 4 (convType va gId rhs)
ppDef (_,(lhs,rhs)) = hang (lhs<+>"=") 4 (convert gId gr rhs) ppDef (_,(lhs,rhs)) = hang (lhs<+>"=") 4 (convert va gId gr rhs)
gId :: Ident -> Doc gId :: Ident -> Doc
gId = if haskellOption opts HaskellNoPrefix then pp else ("G"<>).pp gId = if haskellOption opts HaskellNoPrefix then pp else ("G"<>).pp
va = haskellOption opts HaskellVariants
pure = if va then brackets else pp
neededParamTypes have [] = [] neededParamTypes have [] = []
neededParamTypes have (q:qs) = neededParamTypes have (q:qs) =
if q `S.member` have if q `S.member` have
then neededParamTypes have qs then neededParamTypes have qs
else let ((got,need),def) = paramType gId gr q else let ((got,need),def) = paramType va gId gr q
in def:neededParamTypes (S.union got have) (S.toList need++qs) in def:neededParamTypes (S.union got have) (S.toList need++qs)
haskPreamble :: ModuleName -> ModuleName -> Doc haskPreamble :: Bool -> ModuleName -> ModuleName -> Doc
haskPreamble absname cncname = haskPreamble va absname cncname =
"{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, LambdaCase #-}" $$ "{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, LambdaCase #-}" $$
"module" <+> cncname <+> "where" $$ "module" <+> cncname <+> "where" $$
"import Prelude hiding (Ordering(..))" $$ "import Prelude hiding (Ordering(..))" $$
"import Control.Applicative((<$>),(<*>))" $$ "import Control.Applicative(Applicative,pure,empty,(<$>),(<*>))" $$
--"import Data.Foldable(asum)" $$
--"import Control.Monad(join)" $$
"import qualified Data.Map as M" $$ "import qualified Data.Map as M" $$
"import Data.Map((!))" $$ "import Data.Map((!))" $$
"import PGF.Haskell" $$ "import PGF.Haskell" $$
"import qualified" <+> absname <+> "as A" $$ "import qualified" <+> absname <+> "as A" $$
"" $$ "" $$
"--- Standard definitions ---" $$ "--- Standard definitions ---" $$
"linString (A.GString s) = R_s [TK s]" $$ "linString (A.GString s) ="<+>pure "R_s [TK s]" $$
"linInt (A.GInt i) = R_s [TK (show i)]" $$ "linInt (A.GInt i) ="<+>pure "R_s [TK (show i)]" $$
"linFloat (A.GFloat x) = R_s [TK (show x)]" $$ "linFloat (A.GFloat x) ="<+>pure "R_s [TK (show x)]" $$
"" $$ "" $$
"----------------------------------------------------" $$ "----------------------------------------------------" $$
"-- Automatic translation from GF to Haskell follows" $$ "-- Automatic translation from GF to Haskell follows" $$
"----------------------------------------------------" "----------------------------------------------------"
where
pure = if va then brackets else pp
toHaskell gId gr absname cenv (name,jment) = toHaskell gId gr absname cenv (name,jment) =
case jment of case jment of
@@ -195,32 +203,36 @@ coerce env ty t =
extend env (x,(Just ty,rhs)) = (x,ty):env extend env (x,(Just ty,rhs)) = (x,ty):env
extend env _ = env extend env _ = env
convert gId = convert' False gId convert va gId = convert' False va gId []
convertA gId = convert' True gId convertA va gId = convert' True va gId []
convert' atomic gId gr = if atomic then ppA else ppT convert' atomic va gId vs gr = if atomic then ppA else ppT
where where
ppT0 = convert' False False gId vs gr
ppA0 = convert' True False gId vs gr
ppTv vs' = convert' atomic va gId vs' gr
ppT = ppT' False ppT = ppT' False
ppT' loop t = ppT' loop t =
case t of case t of
Let (x,(_,xt)) t -> sep ["let"<+>x<+>"="<+>ppT xt,"in"<+>ppT t] Let (x,(_,xt)) t -> sep ["let"<+>x<+>"="<+>ppT0 xt,"in"<+>ppT t]
Abs b x t -> "\\"<+>x<+>"->"<+>ppT t -- Abs b x t -> "\\"<+>x<+>"->"<+>ppT t
-- V ty ts -> hang "table" 4 (sep [list (enumAll ty),list ts]) -- V ty ts -> hang "table" 4 (sep [list (enumAll ty),list ts])
V ty ts -> hang "table" 4 (dedup ts) V ty ts -> pure (hang "table" 4 (dedup ts))
T (TTyped ty) cs -> hang "\\case" 2 (vcat (map ppCase cs)) T (TTyped ty) cs -> pure (hang "\\case" 2 (vcat (map ppCase cs)))
S t p -> hang (ppB t) 4 (ppA p) S t p -> join (ap t p)
C t1 t2 -> hang (ppA t1<+>"++") 4 (ppA t2) C t1 t2 -> hang (ppA t1<+>concat) 4 (ppA t2)
_ -> ppB' loop t _ -> ppB' loop t
ppCase (p,t) = hang (ppP p <+> "->") 4 (ppT t) ppCase (p,t) = hang (ppP p <+> "->") 4 (ppTv (patVars p++vs) t)
ppB = ppB' False ppB = ppB' False
ppB' loop t = ppB' loop t =
case t of case t of
App f a -> ppB f<+>ppA a App f a -> ap f a
R r -> rcon (map fst r)<+>fsep (fields r) R r -> aps (ppA (rcon (map fst r))) (fields r)
P t l -> ppB (proj l)<+>ppA t P t l -> ap (proj l) t
FV [] -> "error"<+>doubleQuotes "empty variant" FV [] -> empty
_ -> ppA' loop t _ -> ppA' loop t
ppA = ppA' False ppA = ppA' False
@@ -228,19 +240,19 @@ convert' atomic gId gr = if atomic then ppA else ppT
ppA' True t = error $ "Missing case in convert': "++show t ppA' True t = error $ "Missing case in convert': "++show t
ppA' loop t = ppA' loop t =
case t of case t of
Vr x -> pp x Vr x -> if x `elem` vs then pureA (pp x) else pp x
Cn x -> pp x Cn x -> pureA (pp x)
Con c -> gId c Con c -> pureA (gId c)
Sort k -> pp k Sort k -> pureA (pp k)
EInt n -> pp n EInt n -> pureA (pp n)
Q (m,n) -> if m==cPredef Q (m,n) -> if m==cPredef
then ppPredef n then pureA (ppPredef n)
else pp (qual m n) else pp (qual m n)
QC (m,n) -> gId (qual m n) QC (m,n) -> pureA (gId (qual m n))
K s -> token s K s -> pureA (token s)
Empty -> pp "[]" Empty -> pureA (pp "[]")
FV (t:ts) -> "{-variants-}"<>ppA t -- !! FV ts@(_:_) -> variants ts
Alts t' vs -> alts t' vs Alts t' vs -> pureA (alts t' vs)
_ -> parens (ppT' True t) _ -> parens (ppT' True t)
ppPredef n = ppPredef n =
@@ -270,9 +282,9 @@ convert' atomic gId gr = if atomic then ppA else ppT
token s = brackets ("TK"<+>doubleQuotes s) token s = brackets ("TK"<+>doubleQuotes s)
alts t' vs = brackets ("TP" <+> list' (map alt vs) <+> ppT t') alts t' vs = brackets ("TP" <+> list' (map alt vs) <+> ppA0 t')
where where
alt (t,p) = parens (show (pre p)<>","<>ppT t) alt (t,p) = parens (show (pre p)<>","<>ppT0 t)
pre (K s) = [s] pre (K s) = [s]
pre (Strs ts) = concatMap pre ts pre (Strs ts) = concatMap pre ts
@@ -285,6 +297,22 @@ convert' atomic gId gr = if atomic then ppA else ppT
fields = map (ppA.snd.snd) . sort . filter (not.isLockLabel.fst) fields = map (ppA.snd.snd) . sort . filter (not.isLockLabel.fst)
concat = if va then "+++" else "++"
-- pure = if va then \ x -> "pure"<+>parens x else id
-- pureA = if va then \ x -> parens ("pure"<+>x) else id
pure = if va then \ x -> brackets x else id -- forcing the list monad
pureA = pure
ap = if va then \ f x -> hang (ppA f<+>"<*>") 4 (ppA x)
else \ f x -> hang (ppB f) 4 (ppA x)
join = if va then \ x -> parens ("concat"<+>parens x) else id
-- sequence = if va then \ x -> parens ("sequence"<+>parens x) else id
empty = if va then pp "[]" else "error"<+>doubleQuotes "empty variant"
variants = if va then \ ts -> "concat"<+>list ts
else \ (t:_) -> "{-variants-}"<>ppA t -- !!
aps f [] = f
aps f (a:as) = aps (if va then hang (f<+>"<*>") 4 a else hang f 4 a) as
-- enumAll ty = case allParamValues gr ty of Ok ts -> ts -- enumAll ty = case allParamValues gr ty of Ok ts -> ts
list = brackets . fsep . punctuate "," . map ppT list = brackets . fsep . punctuate "," . map ppT
@@ -306,15 +334,22 @@ convert' atomic gId gr = if atomic then ppA else ppT
m = fmap sort (M.fromListWith (++) (zip ts [[i]|i<-is])) m = fmap sort (M.fromListWith (++) (zip ts [[i]|i<-is]))
is = [0..]::[Int] is = [0..]::[Int]
patVars p =
case p of
PV x -> [x]
PAs x p -> x:patVars p
_ -> collectPattOp patVars p
convType = convType' False convType = convType' False
convTypeA = convType' True convTypeA = convType' True
convType' atomic gId = if atomic then ppA else ppT convType' atomic va gId = if atomic then ppA else ppT
where where
ppT = ppT' False ppT = ppT' False
ppT' loop t = ppT' loop t =
case t of case t of
Table ti tv -> ppB ti <+> "->" <+> ppT tv Table ti tv -> ppB ti <+> "->" <+>
if va then brackets (ppT tv) else ppT tv
_ -> ppB' loop t _ -> ppB' loop t
ppB = ppB' False ppB = ppB' False
@@ -372,7 +407,7 @@ labelClass l =
hang ("class"<+>"Has_"<>l<+>"r"<+>"a"<+>"| r -> a"<+>"where") 4 hang ("class"<+>"Has_"<>l<+>"r"<+>"a"<+>"| r -> a"<+>"where") 4
(proj l<+>"::"<+>"r -> a") (proj l<+>"::"<+>"r -> a")
paramType gId gr q@(_,n) = paramType va gId gr q@(_,n) =
case lookupOrigInfo gr q of case lookupOrigInfo gr q of
Ok (m,ResParam (Just (L _ ps)) _) Ok (m,ResParam (Just (L _ ps)) _)
{- - | m/=cPredef && m/=moduleNameS "Prelude"-} -> {- - | m/=cPredef && m/=moduleNameS "Prelude"-} ->
@@ -389,10 +424,10 @@ paramType gId gr q@(_,n) =
"type"<+>gId (qual m n)<+>"n = Int") "type"<+>gId (qual m n)<+>"n = Int")
| otherwise -> | otherwise ->
((S.singleton (m,n),paramTypes gr t), ((S.singleton (m,n),paramTypes gr t),
"type"<+>gId (qual m n)<+>"="<+>convType gId t) "type"<+>gId (qual m n)<+>"="<+>convType va gId t)
_ -> ((S.empty,S.empty),empty) _ -> ((S.empty,S.empty),empty)
where where
param m (n,ctx) = gId (qual m n)<+>[convTypeA gId t|(_,_,t)<-ctx] param m (n,ctx) = gId (qual m n)<+>[convTypeA va gId t|(_,_,t)<-ctx]
argTypes = S.unions . map argTypes1 argTypes = S.unions . map argTypes1
argTypes1 (n,ctx) = S.unions [paramTypes gr t|(_,_,t)<-ctx] argTypes1 (n,ctx) = S.unions [paramTypes gr t|(_,_,t)<-ctx]

View File

@@ -126,7 +126,7 @@ data CFGTransform = CFGNoLR
deriving (Show,Eq,Ord) deriving (Show,Eq,Ord)
data HaskellOption = HaskellNoPrefix | HaskellGADT | HaskellLexical data HaskellOption = HaskellNoPrefix | HaskellGADT | HaskellLexical
| HaskellConcrete | HaskellConcrete | HaskellVariants
deriving (Show,Eq,Ord) deriving (Show,Eq,Ord)
data Warning = WarnMissingLincat data Warning = WarnMissingLincat
@@ -521,7 +521,8 @@ haskellOptionNames =
[("noprefix", HaskellNoPrefix), [("noprefix", HaskellNoPrefix),
("gadt", HaskellGADT), ("gadt", HaskellGADT),
("lexical", HaskellLexical), ("lexical", HaskellLexical),
("concrete", HaskellConcrete)] ("concrete", HaskellConcrete),
("variants", HaskellVariants)]
-- | This is for bacward compatibility. Since GHC 6.12 we -- | This is for bacward compatibility. Since GHC 6.12 we
-- started using the native Unicode support in GHC but it -- started using the native Unicode support in GHC but it

View File

@@ -2,7 +2,7 @@
-- with @gf -output-format=haskell -haskell=concrete@ -- with @gf -output-format=haskell -haskell=concrete@
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}
module PGF.Haskell where module PGF.Haskell where
import Control.Applicative((<$>)) import Control.Applicative((<$>),(<*>))
import Data.Char(toUpper) import Data.Char(toUpper)
import Data.List(isPrefixOf) import Data.List(isPrefixOf)
import qualified Data.Map as M import qualified Data.Map as M
@@ -54,9 +54,15 @@ class Has_s r a | r -> a where proj_s :: r -> a
-- | Haskell representation of the GF record type @{s:t}@ -- | Haskell representation of the GF record type @{s:t}@
data R_s t = R_s t deriving (Eq,Ord,Show) data R_s t = R_s t deriving (Eq,Ord,Show)
instance (EnumAll t) => EnumAll (R_s t) where instance (EnumAll t) => EnumAll (R_s t) where enumAll = R_s <$> enumAll
enumAll = (R_s <$> enumAll)
instance Has_s (R_s t) t where proj_s (R_s t) = t instance Has_s (R_s t) t where proj_s (R_s t) = t
-- | Coerce from any record type @{...,s:t,...}@ field to the supertype @{s:t}@ -- | Coerce from any record type @{...,s:t,...}@ field to the supertype @{s:t}@
to_R_s r = R_s (proj_s r) to_R_s r = R_s (proj_s r)
-- *** Variants
infixr 5 +++
xs +++ ys = (++) <$> xs <*> ys