packages feed

penrose-0.1.1.1: src/Penrose/SubstanceJSON.hs

{-# LANGUAGE DeriveGeneric #-}
{-# OPTIONS_HADDOCK prune #-}

module Penrose.SubstanceJSON where

import           Data.Aeson
import qualified Data.ByteString.Lazy as BL
import qualified Data.List            as L
import           Debug.Trace
import           GHC.Generics

import qualified Penrose.Env          as E
import           Penrose.Substance

-------------------------------------------------------
-- | JSON schemas and derivations using Aeson
data FunctionSchema = FunctionSchema
  { varName   :: String
  , fname     :: String
  , fargNames :: [String]
  } deriving (Generic, Show)

data PredicateSchema = PredicateSchema
  { pname     :: String
  , pargNames :: [String]
  } deriving (Generic, Show)

data ConstraintSchema = ConstraintSchema
  { functions  :: [FunctionSchema]
  , predicates :: [PredicateSchema]
  } deriving (Generic, Show)

data ObjectSchema = ObjectSchema
  { objType :: String
  , objName :: String
  } deriving (Generic, Show)

data SubSchema = SubSchema
  { objects     :: [ObjectSchema]
  , constraints :: ConstraintSchema
  } deriving (Generic, Show)

instance ToJSON ObjectSchema where
  toEncoding = genericToEncoding defaultOptions

instance FromJSON ObjectSchema

instance ToJSON PredicateSchema where
  toEncoding = genericToEncoding defaultOptions

instance FromJSON PredicateSchema

instance ToJSON FunctionSchema where
  toEncoding = genericToEncoding defaultOptions

instance FromJSON FunctionSchema

instance ToJSON ConstraintSchema where
  toEncoding = genericToEncoding defaultOptions

instance FromJSON ConstraintSchema

instance ToJSON SubSchema where
  toEncoding = genericToEncoding defaultOptions

instance FromJSON SubSchema

-------------------------------------------------------
targToSchema :: E.Arg -> String
targToSchema (E.AVar (E.VarConst s)) = s
targToSchema (E.AT t)                = tToSchema t

tToSchema :: E.T -> String
tToSchema (E.TTypeVar typeVar) = E.typeVarName typeVar
tToSchema (E.TConstr typeCtorApp) =
  let ctorString = E.nameCons typeCtorApp
      args = E.argCons typeCtorApp
  in case args of
       [] -> ctorString -- "Set"
       _ ->
         ctorString ++
         "(" ++ (L.intercalate ", " $ map targToSchema args) ++ ")"

exprToSchema :: Expr -> String
exprToSchema (VarE (E.VarConst v)) = v
exprToSchema _ =
  error
    "Cannot convert anonymous expressions in function/val ctor arguments to JSON; must name them first"

prednameToSchema :: PredicateName -> String
prednameToSchema (PredicateConst s) = s

predargToSchema :: PredArg -> String
predargToSchema (PE expr) = exprToSchema expr
predargToSchema (PP pred) =
  error "Cannot convert nested predicate in predicate argument to JSON"

-- | Convert a Substance statement to a JSON format and adds it to the right list
-- | Note: do not rely on ordering in JSON, as this function does not guarantee preserving Substance program order.
-- | However, we do guarantee preserving argument order in function/valcons/predicate applications.
toSchema ::
     ([ObjectSchema], [FunctionSchema], [PredicateSchema])
  -> SubStmt
  -> ([ObjectSchema], [FunctionSchema], [PredicateSchema])
toSchema acc@(objSchs, fnSchs, predSchs) subLine =
  case subLine of
    Decl t (E.VarConst v) ->
      let res = ObjectSchema {objType = tToSchema t, objName = v}
      in (res : objSchs, fnSchs, predSchs)
    DeclList t vs ->
      let decls = map (\v -> Decl t v) vs
      in foldl toSchema acc decls
    Bind (E.VarConst v) (ApplyFunc f) ->
      let res =
            FunctionSchema
            { varName = v
            , fname = nameFunc f
            , fargNames = map exprToSchema $ argFunc f
            }
      in (objSchs, res : fnSchs, predSchs)
    Bind (E.VarConst v) (ApplyValCons f) ->
      let res =
            FunctionSchema
            { varName = v
            , fname = nameFunc f
            , fargNames = map exprToSchema $ argFunc f
            }
      in (objSchs, res : fnSchs, predSchs)
    ApplyP p ->
      let res =
            PredicateSchema
            { pname = prednameToSchema $ predicateName p
            , pargNames = map predargToSchema $ predicateArgs p
            }
      in (objSchs, fnSchs, res : predSchs)
         -- TODO: these forms are not sent to plugins
    Bind _ (VarE _) ->
      trace "WARNING: not sending Substance form to plugin!" acc
    Bind _ (DeconstructorE _) ->
      trace "WARNING: not sending Substance form to plugin!" acc
    EqualE _ _ -> trace "WARNING: not sending Substance form to plugin!" acc
    EqualQ _ _ -> trace "WARNING: not sending Substance form to plugin!" acc
    LabelDecl _ _ -> acc
    AutoLabel _ -> acc
    NoLabel _ -> acc

-- | Turn a Substance prog into the schema format defined above
subToSchema :: SubProg -> SubSchema
subToSchema prog =
  let (objSchs, fnSchs, predSchs) = foldl toSchema ([], [], []) prog
  in SubSchema
     { objects = objSchs
     , constraints =
         ConstraintSchema {functions = fnSchs, predicates = predSchs}
     }

-- | This is the main function for converting a parsed Substance program to JSON format, called in ShadowMain
writeSubstanceToJSON :: FilePath -> SubOut -> IO ()
writeSubstanceToJSON file (SubOut subprog envs labels) = do
  let substanceSchema = subToSchema subprog
  let bytestr = encode substanceSchema
  BL.writeFile file bytestr

--------------------------------------------------------
-- | JSON format for parsing Style values from plugins
data KeyValPair = KeyValPair
  { propertyName :: String
  , propertyVal  :: Float -- TODO: generalize this
  } deriving (Generic, Show)

data StyVal = StyVal
  { subName  :: String
  , nameVals :: [KeyValPair]
  } deriving (Generic, Show)

instance FromJSON KeyValPair

instance FromJSON StyVal

-- Plugin output parsed as [StyVal]
--------------------------------------------------------
-- | Test writing a Substance program in JSON format
main :: IO ()
main
     -- let subProg = "Set A\nSet B\n IsSubset(A,B)\nSet C\nC := Union(A, B)\n\nPoint p\n PointIn(C, p)\nC := AddPoint(p, C)\n\nAutoLabel All"
 = do
  let testFile = "testSubstanceJSON.json"
  let info =
        SubSchema
        { objects = [ObjectSchema {objType = "Set", objName = "A"}]
        , constraints =
            ConstraintSchema
            { functions =
                [ FunctionSchema
                  {varName = "C", fname = "Union", fargNames = ["A", "B"]}
                ]
            , predicates =
                [PredicateSchema {pname = "IsSubset", pargNames = ["C", "p"]}]
            }
        }
  let res = encode info
  BL.writeFile testFile res