packages feed

lol-typing-1.20160822: Language/LOL/Typing/Type/Synotype.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
module Language.LOL.Typing.Type.Synotype where

import Control.Monad (forM)
import Data.Bool
import Data.Eq (Eq(..))
import qualified Data.Foldable as Foldable
import Data.Function (($), (.), id)
import Data.Functor ((<$>))
import qualified Data.Graph as Graph (scc, buildG)
import Data.Int (Int)
import qualified Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Data.Monoid (Monoid(..), (<>))
import Data.Ord (Ord(..))
import qualified Data.Text as Text
import Data.Text.Buildable (Buildable(..))
import qualified Data.Tree as Tree (flatten)
import Data.Tuple (uncurry)
import Prelude (Num(..), error)
import Text.Show (Show(..), showChar, showParen, showString)

import Language.LOL.Typing.Type.Monotype
import Language.LOL.Typing.Type.Substitution
import Language.LOL.Typing.Lib.Data.Empty (Empty(..))

-- * Type 'Synotype'

-- | An (unordered) collection of /type synonyms/.
data Synotype
 = Synotype
 { synotype_arity    :: Arity
 , synotype_monotype :: [Monotype] -> Monotype
 }
type Synotypes
 = Map Synoname Synotype

type Synoname = Monoconst

-- | An ordering of 'Synotypes'.
type Synotype_Ordering
 = Map Synoname Synotype_Position

-- | A position in a 'Synotype_Ordering'.
type Synotype_Position = Int

-- | 'Synotypes' with a 'Synotype_Ordering'.
data Synotype_Substitution
 =   Synotype_Substitution
 {   synotypes_ordering :: Synotype_Ordering
 ,   synotypes          :: Synotypes
 }
instance Show Synotype_Substitution where
	showsPrec d Synotype_Substitution{synotypes_ordering} =
		showParen (d > 10) $
		showString "Synotype_Substitution" .
		showChar ' ' .
		showChar '[' .
		showString
		 (mconcat $ List.intersperse ", " $
			Text.unpack <$> Map.keys synotypes_ordering) .
		showChar ']'
instance Buildable Synotype_Substitution where
	build Synotype_Substitution{synotypes_ordering} =
		"[" <>
		mconcat (
			List.intersperse ", " $
			build <$> Map.keys
			synotypes_ordering) <>
		"]"

instance Empty Synotype_Substitution where
	empty = Synotype_Substitution
	 { synotypes_ordering = mempty
	 , synotypes          = mempty
	 }

-- | A 'type_String' is a 'type_List' of 'type_Char's
synotype_String :: Synotype_Substitution
synotype_String =
	Synotype_Substitution
	 (Map.singleton "String" 0)
	 (Map.singleton "String" $ Synotype 0 $ \_ -> type_List type_Char)

-- | Return the 'Synotype_Ordering' of given 'Synotypes'
-- along with the mutually recursive 'Synotypes' that are detected.
synorder
 :: Synotypes
 -> (Synotype_Ordering, [[Synoname]])
synorder sys =
	let keys = Map.keys sys in
	let names = Map.fromList $ List.zip keys [0..] in
	let positions = Map.fromList $ List.zip [0..] keys in
	
	let err = error "synorder: error in lookup table" in
	let name_lookup n = fromMaybe err (Map.lookup n names) in
	let pos_lookup  p = fromMaybe err (Map.lookup p positions) in
	
	let edges = Map.foldrWithKey go [] sys
		where
		go s1 (Synotype ari monoty) es =
			Map.foldrWithKey add es $
			monoconsts $ monoty (Monotype_Var <$> [0 .. ari - 1])
			where
			add s2 _ =
				case Map.lookup s2 names of
				 Just i2 -> (:) (i2, i1)
				 Nothing -> id
				where
				i1 = name_lookup s1
	 in
	
	let graph = Graph.buildG (0, Map.size sys - 1) edges in
	let list  = Tree.flatten <$> Graph.scc graph in
	
	let (ordering, recursive, _) =
		List.foldr go (Map.empty, [], 0) list
		where
		go ps (os, rs, counter) =
			case ps of
			 [p] | (p, p) `List.notElem` edges -> -- NOTE: correct type synotype
				( Map.insert (pos_lookup p) counter os
				, rs
				, counter + 1
				)
			 _ ->
				( os
				, (pos_lookup <$> ps) : rs
				, counter
				)
	 in
	(ordering, recursive)

-- | Return whether given 'Synoname'
-- 
is_panthom_synotype :: Synotype_Substitution -> Synoname -> Bool
is_panthom_synotype Synotype_Substitution{synotypes} name =
	case Map.lookup name synotypes of
	 Nothing -> False
	 Just (Synotype ari monoty) ->
		let args = List.take ari [0..] in
		let subvars_ty = subvars $ monoty (Monotype_Var <$> args) in
		Foldable.any (`List.notElem` subvars_ty) args

-- | Fully expand a type in a recursive way.
synexpand :: Synotypes -> Monotype -> Monotype
synexpand sys ty =
	top_expanded `monoapp` (synexpand sys <$> tail)
	where top_expanded `App_Spine` tail =
		app_spine_left $ synexpand_tops sys ty

-- | Fully expand the top-level 'Monotype_Const's.
synexpand_tops :: Synotypes -> Monotype -> Monotype
synexpand_tops sys ty =
	maybe ty (synexpand_tops sys) $
	synexpand_top_step sys ty

{-
-- | Fully expand the top-level 'Monotype_Const'.
synexpand_top :: Synotype_Substitution -> Monotype -> Maybe Monotype
synexpand_top Synotype_Substitution{synotypes} =
	(synexpand_tops synotypes <$>) .
	synexpand_top_step synotypes
-}

-- | Try to expand the top-level 'Monotype_Const' one step.
synexpand_top_step :: Synotypes -> Monotype -> Maybe Monotype
synexpand_top_step sys ty =
	case app_spine_left ty of
	 Monotype_Const c `App_Spine` args ->
		case Map.lookup c sys of
		 Just (Synotype ari monoty)
		  | ari == List.length args -> Just (monoty args)
		  | otherwise -> err "synotype arity mismatch"
		 Nothing -> Nothing
	 _ -> Nothing
	where
		err s = error $ "synexpand_top_step: " <> s

-- | Try to expand the top-level 'Monotype_Const'
-- of one of the two paired 'Monotype's.
--
-- If both top-level 'Monotype_Const' can be expanded one step,
-- then the 'Synotype' which appears first
-- in the 'Synotype_Ordering' is expanded.
synexpand_top_step_ordered
 :: Synotype_Substitution
 -> (Monotype, Monotype)
 -> Maybe (Monotype, Monotype)
synexpand_top_step_ordered
 Synotype_Substitution{synotypes_ordering, synotypes}
 (ty1, ty2) =
	case (position ty1, position ty2) of
	 (Just p1, Just p2) | p1 <= p2  -> Just (expand_top_step ty1, ty2)
	                    | otherwise -> Just (ty1, expand_top_step ty2)
	 (Just _ , Nothing) -> Just (expand_top_step ty1, ty2)
	 (Nothing, Just _ ) -> Just (ty1, expand_top_step ty2)
	 _ -> Nothing
	where
	position ty =
		case app_spine_left ty of
		 Monotype_Const c `App_Spine` _ -> Map.lookup c synotypes_ordering
		 _ -> Nothing
	expand_top_step ty =
		fromMaybe
		 (error "synexpand_top_step_ordered: Synotype_Substitution is invalid")
		 (synexpand_top_step synotypes ty)

-- | Return the /most general type/
-- (i.e. with the least number of expansions).
-- of two 'Monotype's which must be equal
-- under given 'Synotype_Substitution'.
mgt_with_synotypes
 :: Synotype_Substitution
 -> Monotype -> Monotype
 -> Maybe Monotype
mgt_with_synotypes syns t1 t2 =
	case (app_spine_left t1, app_spine_left t2) of
	 (  Monotype_Var v `App_Spine` []
	  , Monotype_Var _ `App_Spine` [] )
	  -> Just $ Monotype_Var v
	 (  Monotype_Const c1 `App_Spine` tail1
	  , Monotype_Const c2 `App_Spine` tail2 )
	  | c1 == c2 && not (is_panthom_synotype syns c1)
	  -> do
		mgts <-
			forM (List.zip tail1 tail2) $ uncurry $
			mgt_with_synotypes syns
		Just (Monotype_Const c1 `monoapp` mgts)
	  | otherwise -> do
		(t1', t2') <- synexpand_top_step_ordered syns (t1, t2)
		mgt_with_synotypes syns t1' t2'
	 _ -> Nothing