glance/app/Translate.hs

416 lines
17 KiB
Haskell
Raw Normal View History

{-# LANGUAGE NoMonomorphismRestriction, FlexibleContexts, TypeFamilies #-}
module Translate(
translateString
) where
import qualified Diagrams.Prelude as DIA
import Diagrams.Prelude((<>))
2016-02-19 09:51:16 +03:00
import Language.Haskell.Exts(Decl(..), parseDecl, Name(..), Pat(..), Rhs(..),
Exp(..), QName(..), fromParseResult, Match(..), QOp(..), GuardedRhs(..),
2016-02-21 05:47:56 +03:00
Stmt(..), Binds(..))
2016-02-19 07:34:08 +03:00
import qualified Language.Haskell.Exts as Exts
2016-02-06 08:07:06 +03:00
import Control.Monad.State(State, evalState)
2016-02-21 05:47:56 +03:00
import Debug.Trace
2016-02-22 02:15:16 +03:00
import Data.Either(partitionEithers)
2016-02-08 05:01:57 +03:00
import Types(Icon, Edge(..), Drawing(..), NameAndPort(..), IDState,
2016-02-06 08:07:06 +03:00
initialIdState, getId)
import Util(toNames, noEnds, nameAndPort, justName)
import Icons(Icon(..))
type Reference = Either String NameAndPort
-- | An IconGraph is a normal Drawing (Icons, Edges, and sub Drawings) with two additional fields:
-- unconected sink ports (varible usage), and unconnected source ports (varible definition).
data IconGraph = IconGraph [(DIA.Name, Icon)] [Edge] [(DIA.Name, Drawing)] [(String, NameAndPort)] [(String, Reference)]
2016-02-21 05:47:56 +03:00
deriving (Show)
2016-02-08 05:01:57 +03:00
type EvalContext = [String]
type GraphAndRef = (IconGraph, Reference)
type Sink = (String, NameAndPort)
instance DIA.Semigroup IconGraph where
(IconGraph icons1 edges1 subDrawings1 sinks1 sources1) <> (IconGraph icons2 edges2 subDrawings2 sinks2 sources2) =
IconGraph (icons1 <> icons2) (edges1 <> edges2) (subDrawings1 <> subDrawings2) (sinks1 <> sinks2) (sources1 <> sources2)
instance Monoid IconGraph where
mempty = IconGraph mempty mempty mempty mempty mempty
mappend = (<>)
iconGraphFromIcons :: [(DIA.Name, Icon)] -> IconGraph
iconGraphFromIcons icons = IconGraph icons mempty mempty mempty mempty
iconGraphFromIconsEdges :: [(DIA.Name, Icon)] -> [Edge] -> IconGraph
iconGraphFromIconsEdges icons edges = IconGraph icons edges mempty mempty mempty
2016-02-18 10:14:14 +03:00
getUniqueName :: String -> State IDState String
getUniqueName base = fmap ((base ++). show) getId
nameToString :: Language.Haskell.Exts.Name -> String
nameToString (Ident s) = s
nameToString (Symbol s) = s
evalPApp :: QName -> [Pat] -> State IDState (IconGraph, NameAndPort)
evalPApp name patterns = do
patName <- DIA.toName <$> getUniqueName "pat"
let
context = mempty
evaledPatterns <- mapM evalPattern patterns
let
constructorName = evalQName name context
gr = makeApplyGraph True patName constructorName evaledPatterns (length evaledPatterns)
pure gr
evalPLit :: Exts.Sign -> Exts.Literal -> State IDState (IconGraph, NameAndPort)
evalPLit Exts.Signless l = evalLit l
evalPLit Exts.Negative l = makeBox ('-' : showLiteral l)
evalPattern :: Pat -> State IDState GraphAndRef
evalPattern p = case p of
PVar n -> pure (mempty, Left $ nameToString n)
PLit s l -> fmap Right <$> evalPLit s l
PApp name patterns -> fmap Right <$> evalPApp name patterns
PParen pat -> evalPattern pat
evalQName :: QName -> EvalContext -> (IconGraph, Reference)
2016-02-08 05:01:57 +03:00
evalQName (UnQual n) context = result where
nameString = nameToString n
graph = iconGraphFromIcons [(DIA.toName nameString, TextBoxIcon nameString)]
2016-02-08 05:01:57 +03:00
result = if nameString `elem` context
then (mempty, Left nameString)
else (graph, Right $ justName nameString)
evalQOp :: QOp -> EvalContext -> (IconGraph, Reference)
2016-02-19 09:07:38 +03:00
evalQOp (QVarOp n) = evalQName n
evalQOp (QConOp n) = evalQName n
combineExpressions :: Bool -> [((IconGraph, Reference), NameAndPort)] -> IconGraph
combineExpressions inPattern portExpPairs = mconcat $ fmap mkGraph portExpPairs where
mkGraph ((graph, ref), port) = graph <> case ref of
Left str -> if inPattern
then IconGraph mempty mempty mempty mempty [(str, Right port)]
else IconGraph mempty mempty mempty [(str, port)] mempty
Right resultPort -> IconGraph mempty [Edge (resultPort, port) noEnds] mempty mempty mempty
2016-02-18 02:36:57 +03:00
makeApplyGraph :: Bool -> DIA.Name -> (IconGraph, Reference) -> [(IconGraph, Reference)] -> Int -> (IconGraph, NameAndPort)
makeApplyGraph inPattern applyIconName funVal argVals numArgs = (newGraph <> combinedGraph, nameAndPort applyIconName 1)
2016-02-19 09:07:38 +03:00
where
argumentPorts = map (nameAndPort applyIconName) [2,3..]
functionPort = nameAndPort applyIconName 0
combinedGraph = combineExpressions inPattern $ zip (funVal:argVals) (functionPort:argumentPorts)
2016-02-19 09:07:38 +03:00
icons = [(applyIconName, Apply0NIcon numArgs)]
newGraph = iconGraphFromIcons icons
2016-02-19 09:07:38 +03:00
2016-02-19 02:03:31 +03:00
evalApp :: (Exp, [Exp]) -> EvalContext -> State IDState (IconGraph, NameAndPort)
2016-02-18 07:59:43 +03:00
evalApp (funExp, argExps) c = do
2016-02-10 05:58:28 +03:00
funVal <- evalExp c funExp
argVals <- mapM (evalExp c) argExps
2016-02-18 10:14:14 +03:00
applyIconName <- DIA.toName <$> getUniqueName "app0"
pure $ makeApplyGraph False applyIconName funVal argVals (length argExps)
2016-02-19 09:07:38 +03:00
evalInfixApp :: EvalContext -> Exp -> QOp -> Exp -> State IDState (IconGraph, NameAndPort)
evalInfixApp c e1 op e2 = do
argVals <- mapM (evalExp c) [e1, e2]
applyIconName <- DIA.toName <$> getUniqueName "app0"
let funVal = evalQOp op c
pure $ makeApplyGraph False applyIconName funVal argVals 2
2016-02-10 05:58:28 +03:00
-- TODO add test for this function
simplifyApp :: Exp -> (Exp, [Exp])
simplifyApp (App exp1 exp2) = (funExp, args <> [exp2])
where
(funExp, args) = simplifyApp exp1
simplifyApp e = (e, [])
2016-02-06 08:07:06 +03:00
2016-02-18 10:14:14 +03:00
evalIf :: EvalContext -> Exp -> Exp -> Exp -> State IDState (IconGraph, NameAndPort)
evalIf c e1 e2 e3 = do
e1Val <- evalExp c e1
e2Val <- evalExp c e2
e3Val <- evalExp c e3
guardName <- DIA.toName <$> getUniqueName "if"
let
icons = [(guardName, GuardIcon 2)]
combinedGraph =
combineExpressions False $ zip [e1Val, e2Val, e3Val] (map (nameAndPort guardName) [3, 2, 4])
newGraph = iconGraphFromIcons icons <> combinedGraph
2016-02-18 10:14:14 +03:00
pure (newGraph, NameAndPort guardName (Just 0))
2016-02-18 02:36:57 +03:00
evalStmt :: EvalContext -> Stmt -> State IDState GraphAndRef
2016-02-19 09:51:16 +03:00
evalStmt c (Qualifier e) = evalExp c e
evalStmts :: EvalContext -> [Stmt] -> State IDState GraphAndRef
2016-02-19 09:51:16 +03:00
evalStmts c [stmt] = evalStmt c stmt
evalGuaredRhs :: EvalContext -> GuardedRhs -> State IDState (GraphAndRef, GraphAndRef)
2016-02-19 09:51:16 +03:00
evalGuaredRhs c (GuardedRhs _ stmts e) = do
expVal <- evalExp c e
stmtsVal <- evalStmts c stmts
pure (stmtsVal, expVal)
evalGuardedRhss :: EvalContext -> [GuardedRhs] -> State IDState (IconGraph, NameAndPort)
evalGuardedRhss c rhss = do
guardName <- DIA.toName <$> getUniqueName "guard"
evaledRhss <- mapM (evalGuaredRhs c) rhss
let
(bools, exps) = unzip evaledRhss
expsWithPorts = zip exps $ map (nameAndPort guardName) [2,4..]
boolsWithPorts = zip bools $ map (nameAndPort guardName) [3,5..]
combindedGraph = combineExpressions False $ expsWithPorts <> boolsWithPorts
2016-02-19 09:51:16 +03:00
icons = [(guardName, GuardIcon (length rhss))]
newGraph = iconGraphFromIcons icons <> combindedGraph
2016-02-19 09:51:16 +03:00
pure (newGraph, NameAndPort guardName (Just 0))
makeBox :: String -> State IDState (IconGraph, NameAndPort)
makeBox str = do
2016-02-19 07:34:08 +03:00
name <- DIA.toName <$> getUniqueName str
let graph = iconGraphFromIcons [(DIA.toName name, TextBoxIcon str)]
2016-02-19 07:34:08 +03:00
pure (graph, justName name)
makeLiteral :: (Show x) => x -> State IDState (IconGraph, NameAndPort)
makeLiteral = makeBox. show
2016-02-20 00:46:14 +03:00
evalLit :: Exts.Literal -> State IDState (IconGraph, NameAndPort)
evalLit (Exts.Int x) = makeLiteral x
evalLit (Exts.Char x) = makeLiteral x
evalLit (Exts.String x) = makeLiteral x
-- TODO: Print the Rational as a floating point.
evalLit (Exts.Frac x) = makeLiteral x
-- TODO: Test the unboxed literals
evalLit (Exts.PrimInt x) = makeLiteral x
evalLit (Exts.PrimWord x) = makeLiteral x
evalLit (Exts.PrimFloat x) = makeLiteral x
evalLit (Exts.PrimDouble x) = makeLiteral x
evalLit (Exts.PrimChar x) = makeLiteral x
evalLit (Exts.PrimString x) = makeLiteral x
showLiteral :: Exts.Literal -> String
showLiteral (Exts.Int x) = show x
showLiteral (Exts.Char x) = show x
showLiteral (Exts.String x) = show x
-- TODO: Print the Rational as a floating point.
showLiteral (Exts.Frac x) = show x
-- TODO: Test the unboxed literals
showLiteral (Exts.PrimInt x) = show x
showLiteral (Exts.PrimWord x) = show x
showLiteral (Exts.PrimFloat x) = show x
showLiteral (Exts.PrimDouble x) = show x
showLiteral (Exts.PrimChar x) = show x
showLiteral (Exts.PrimString x) = show x
namesInPattern :: GraphAndRef -> [String]
namesInPattern (_, Left str) = [str]
namesInPattern (IconGraph _ _ _ _ bindings, Right _) = fmap fst bindings
getBoundVarName :: Decl -> [String]
-- TODO Should evalState be used here?
getBoundVarName (PatBind _ pat _ _) = namesInPattern $ evalState (evalPattern pat) initialIdState
getBoundVarName (FunBind [Match _ name _ _ _ _]) = [nameToString name]
2016-02-21 05:47:56 +03:00
2016-02-22 02:15:16 +03:00
--TODO: Should this call makeEdges?
2016-02-21 09:35:13 +03:00
evalBinds :: EvalContext -> Binds -> State IDState (IconGraph, EvalContext)
2016-02-21 05:47:56 +03:00
evalBinds c (BDecls decls) = do
let
boundNames = concatMap getBoundVarName decls
2016-02-21 05:47:56 +03:00
augmentedContext = boundNames <> c
2016-02-21 09:35:13 +03:00
evaledDecl <- mconcat <$> mapM (evalDecl augmentedContext) decls
pure (evaledDecl, augmentedContext)
2016-02-21 05:47:56 +03:00
printSelf :: (Show a) => a -> a
printSelf a = Debug.Trace.trace (show a ++ "\n\n") a
2016-02-21 10:07:46 +03:00
-- | Recursivly find the matching reference in a list of bindings.
-- TODO: Might want to present some indication if there is a reference cycle.
2016-02-21 09:35:13 +03:00
lookupReference :: [(String, Reference)] -> Reference -> Reference
2016-02-22 02:15:16 +03:00
lookupReference _ ref@(Right _) = ref
lookupReference bindings ref@(Left originalS) = lookupHelper ref where
2016-02-22 02:15:16 +03:00
lookupHelper newRef@(Right _) = newRef
lookupHelper newRef@(Left s)= case lookup s bindings of
Just r -> failIfCycle r $ lookupHelper r
2016-02-22 02:15:16 +03:00
Nothing -> newRef
where
failIfCycle r@(Left newStr) res = if newStr == originalS then r else res
failIfCycle _ res = res
2016-02-21 09:35:13 +03:00
deleteBindings :: IconGraph -> IconGraph
deleteBindings (IconGraph a b c d _) = IconGraph a b c d mempty
makeEdgesCore :: [Sink] -> [(String, Reference)] -> ([Sink], [Edge])
makeEdgesCore sinks bindings = partitionEithers $ fmap renameOrMakeEdge sinks
where
renameOrMakeEdge :: (String, NameAndPort) -> Either (String, NameAndPort) Edge
renameOrMakeEdge orig@(s, destPort) = case lookup s bindings of
Just ref -> case lookupReference bindings ref of
(Right sourcePort) -> Right $ Edge (sourcePort, destPort) noEnds
(Left newStr) -> Left (newStr, destPort)
Nothing -> Left orig
2016-02-22 02:15:16 +03:00
makeEdges :: IconGraph -> IconGraph
makeEdges (IconGraph icons edges c sinks bindings) = newGraph where
(newSinks, newEdges) = makeEdgesCore sinks bindings
2016-02-22 02:15:16 +03:00
newGraph = IconGraph icons (newEdges <> edges) c newSinks bindings
2016-02-22 06:34:33 +03:00
evalGeneralLet :: (EvalContext -> State IDState (IconGraph, Reference)) -> EvalContext -> Binds -> State IDState (IconGraph, Reference)
evalGeneralLet expOrRhsEvaler c bs = do
2016-02-21 09:35:13 +03:00
(bindGraph, bindContext) <- evalBinds c bs
2016-02-22 06:34:33 +03:00
expVal <- expOrRhsEvaler bindContext
2016-02-21 05:47:56 +03:00
let
2016-02-21 09:35:13 +03:00
(expGraph, expResult) = expVal
2016-02-22 02:15:16 +03:00
newGraph = deleteBindings . makeEdges $ expGraph <> bindGraph
(IconGraph _ _ _ _ bindings) = bindGraph
pure (newGraph, lookupReference bindings expResult)
2016-02-20 00:46:14 +03:00
2016-02-22 06:34:33 +03:00
evalLet :: EvalContext -> Binds -> Exp -> State IDState (IconGraph, Reference)
evalLet context binds e = evalGeneralLet (`evalExp` e) context binds
evalExp :: EvalContext -> Exp -> State IDState (IconGraph, Reference)
2016-02-10 05:58:28 +03:00
evalExp c x = case x of
2016-02-08 05:01:57 +03:00
Var n -> pure $ evalQName n c
Con n -> pure $ evalQName n c
Lit l -> fmap Right <$> evalLit l
InfixApp e1 op e2 -> fmap Right <$> evalInfixApp c e1 op e2
e@App{} -> fmap Right <$> evalApp (simplifyApp e) c
Lambda _ patterns e -> fmap Right <$> evalLambda c patterns e
2016-02-21 09:35:13 +03:00
Let bs e -> evalLet c bs e
If e1 e2 e3 -> fmap Right <$> evalIf c e1 e2 e3
2016-02-21 05:47:56 +03:00
Paren e -> evalExp c e
2016-02-10 09:29:07 +03:00
-- | This is used by the rhs for identity (eg. y x = x)
makeDummyRhs :: String -> (IconGraph, NameAndPort)
makeDummyRhs s = (graph, port) where
graph = IconGraph icons mempty mempty [(s, justName s)] mempty
2016-02-10 09:29:07 +03:00
icons = [(DIA.toName s, BranchIcon)]
port = justName s
coerceExpressionResult :: (IconGraph, Reference) -> (IconGraph, NameAndPort)
coerceExpressionResult (_, Left str) = makeDummyRhs str
coerceExpressionResult (g, Right x) = (g, x)
2016-02-18 02:36:57 +03:00
2016-02-08 05:01:57 +03:00
-- | First argument is the right hand side.
-- The second arugement is a list of strings that are bound in the environment.
evalRhs :: EvalContext -> Rhs -> State IDState (IconGraph, Reference)
evalRhs c (UnGuardedRhs e) = evalExp c e
evalRhs c (GuardedRhss rhss) = fmap Right <$> evalGuardedRhss c rhss
rhsWithBinds :: Maybe Binds -> Rhs -> EvalContext -> State IDState (IconGraph, Reference)
rhsWithBinds maybeWhereBinds rhs rhsContext = case maybeWhereBinds of
Nothing -> evalRhs rhsContext rhs
Just b -> evalGeneralLet (`evalRhs` rhs) rhsContext b
2016-02-21 05:47:56 +03:00
evalPatBind :: EvalContext -> Decl -> State IDState IconGraph
evalPatBind c (PatBind _ pat rhs maybeWhereBinds) = do
patternNames <- namesInPattern <$> evalPattern pat
let rhsContext = patternNames <> c
(rhsGraph, rhsRef) <- rhsWithBinds maybeWhereBinds rhs rhsContext
(patGraph, patRef) <- evalPattern pat
let
(newEdges, newSinks, bindings) = case patRef of
(Left s) -> (mempty, mempty, [(s, rhsRef)])
(Right patPort) -> case rhsRef of
(Left rhsStr) -> (mempty, [(rhsStr, patPort)], mempty)
-- TODO: This edge should be special to indicate that one side is a pattern.
(Right rhsPort) -> ([Edge (rhsPort, patPort) noEnds], mempty, mempty)
gr = IconGraph mempty newEdges mempty newSinks bindings
pure . makeEdges $ (gr <> rhsGraph <> patGraph)
2016-02-22 06:34:33 +03:00
2016-02-08 05:01:57 +03:00
iconGraphToDrawing :: IconGraph -> Drawing
iconGraphToDrawing (IconGraph icons edges subDrawings _ _) = Drawing icons edges subDrawings
2016-02-08 05:01:57 +03:00
2016-02-18 05:51:03 +03:00
makeRhsDrawing :: DIA.IsName a => a -> (IconGraph, NameAndPort) -> Drawing
makeRhsDrawing resultIconName (rhsGraph, rhsResult)= rhsDrawing where
rhsNewIcons = toNames [(resultIconName, ResultIcon)]
rhsNewEdges = [Edge (rhsResult, justName resultIconName) noEnds]
rhsGraphWithResult = rhsGraph <> iconGraphFromIconsEdges rhsNewIcons rhsNewEdges
2016-02-18 05:51:03 +03:00
rhsDrawing = iconGraphToDrawing rhsGraphWithResult
qualifyNameAndPort :: String -> NameAndPort -> NameAndPort
qualifyNameAndPort s (NameAndPort n p) = NameAndPort (s DIA..> n) p
2016-02-23 02:45:53 +03:00
makePatternEdges :: String -> GraphAndRef -> NameAndPort -> Either IconGraph (String, Reference)
makePatternEdges lambdaName (_, Right patPort) lamPort =
Left $ iconGraphFromIconsEdges mempty
[Edge (lamPort, qualifyNameAndPort lambdaName patPort) noEnds]
2016-02-23 02:45:53 +03:00
makePatternEdges _ (_, Left str) lamPort = Right (str, Right lamPort)
generalEvalLambda :: EvalContext -> [Pat] -> (EvalContext -> State IDState GraphAndRef) -> State IDState (IconGraph, NameAndPort)
generalEvalLambda context patterns rhsEvalFun = do
lambdaName <- getUniqueName "lam"
patternVals <- mapM evalPattern patterns
let
patternStrings = concatMap namesInPattern patternVals
rhsContext = patternStrings <> context
lambdaPorts = map (nameAndPort lambdaName) [0,1..]
patternGraph = mconcat $ map fst patternVals
(patternEdgeGraphs, rawNewBinds) =
partitionEithers $ zipWith (makePatternEdges lambdaName) patternVals lambdaPorts
patternEdgeGraph = mconcat patternEdgeGraphs
newBinds = rawNewBinds
numParameters = length patterns
-- TODO remove coerceExpressionResult here
(rhsRawGraph, rhsResult) <- coerceExpressionResult <$> rhsEvalFun rhsContext
resultIconName <- getUniqueName "res"
rhsDrawingName <- DIA.toName <$> getUniqueName "rhsDraw"
let
rhsAndPatternGraph@(IconGraph _ _ _ sinks _) = makeEdges $ patternGraph <> rhsRawGraph
qualifiedSinks = fmap (fmap (qualifyNameAndPort lambdaName)) sinks
(newSinks, internalEdges) = makeEdgesCore qualifiedSinks newBinds
rhsDrawing = makeRhsDrawing resultIconName (rhsAndPatternGraph, rhsResult)
icons = toNames [(lambdaName, LambdaRegionIcon numParameters rhsDrawingName)]
finalGraph = IconGraph icons internalEdges [(rhsDrawingName, rhsDrawing)]
newSinks mempty
pure (patternEdgeGraph <> finalGraph, justName lambdaName)
evalLambda :: EvalContext -> [Pat] -> Exp -> State IDState (IconGraph, NameAndPort)
evalLambda c patterns e = generalEvalLambda c patterns (`evalExp` e)
2016-02-23 02:45:53 +03:00
evalMatch :: EvalContext -> Match -> State IDState IconGraph
evalMatch c (Match _ name patterns _ rhs maybeWhereBinds) = do
let
matchFunNameString = nameToString name
newContext = matchFunNameString : c
(lambdaGraph, lambdaPort) <-
generalEvalLambda newContext patterns (rhsWithBinds maybeWhereBinds rhs)
let
newBinding = IconGraph mempty mempty mempty mempty [(matchFunNameString, Right lambdaPort)]
pure $ makeEdges (newBinding <> lambdaGraph)
2016-02-23 02:45:53 +03:00
2016-02-21 05:47:56 +03:00
evalMatches :: EvalContext -> [Match] -> State IDState IconGraph
2016-02-22 02:15:16 +03:00
evalMatches _ [] = pure mempty
evalMatches c matches = mconcat <$> mapM (evalMatch c) matches
2016-02-08 05:01:57 +03:00
-- TODO turn more than one match into a case expression.
2016-02-21 05:47:56 +03:00
-- TODO: Use the context in evalPatBind and evalMatches
evalDecl :: EvalContext -> Decl -> State IDState IconGraph
evalDecl c d = evaluatedDecl where
2016-02-18 07:59:43 +03:00
evaluatedDecl = case d of
2016-02-21 05:47:56 +03:00
pat@PatBind{} -> evalPatBind c pat
FunBind matches -> evalMatches c matches
showTopLevelBinds :: IconGraph -> State IDState IconGraph
showTopLevelBinds gr@(IconGraph _ _ _ _ binds) = do
let
addBind (_, Left _) = pure mempty
addBind (patName, Right port) = do
uniquePatName <- getUniqueName patName
let
icons = toNames [(uniquePatName, TextBoxIcon patName)]
edges = [Edge (justName uniquePatName, port) noEnds]
edgeGraph = iconGraphFromIconsEdges icons edges
pure edgeGraph
newGraph <- mconcat <$> mapM addBind binds
pure $ newGraph <> gr
2016-02-21 05:47:56 +03:00
drawingFromDecl :: Decl -> Drawing
drawingFromDecl d = iconGraphToDrawing $ evalState evaluatedDecl initialIdState
where evaluatedDecl = evalDecl mempty d >>= showTopLevelBinds
2016-02-21 05:47:56 +03:00
2016-02-19 07:34:08 +03:00
-- Profiling: about 1.5% of time.
translateString :: String -> (Drawing, Decl)
translateString s = (drawing, decl) where
parseResult = parseDecl s -- :: ParseResult Module
decl = fromParseResult parseResult
2016-02-21 05:47:56 +03:00
drawing = drawingFromDecl decl