# LANGUAGE PatternGuards #

-- | AO's 'inline everything' semantics are simple for reasoning, but
-- not efficient. It makes poor reuse of memory, CPU cache, separate
  compilation , and bandwidth . To mitigate this , AO systems leverage
  ABC 's separate compilation and linking model . Any sequence of ABC
-- can be given a cryptographically unique resource token, which may
  then be invoked to logically inline the associated ABC .
--
  See module  for more about the resource model .
--
-- Long term, the idea is that we should use machine learning to chop
  large ABC programs into highly reusable , near - optimal components .
--
-- But this module is concerned with a short term solution. A subset
  of words in the AO dictionary will be treated as ABC resources by
-- replacing their definitions with the appropriate invocation. The
  ABC resources may truly be compiled separately for performance .
-- 
-- The selection of words for compilation is based on convention. At
-- this time, we simply compile every word `foo` for which there is
-- a word `compile!foo` defined in the dictionary. This is rather
-- ad-hoc, but it will do the job well enough for now.
--
-- TODO: eventually, I need to support sensitivity concerns, i.e. to
  compile sensitive ABC modules using an unguessable secret to guard
-- against confirmation attacks.
--
module AO.Precompile 
    ( preCompileDict
    , PreCompD, SecStoreD
    ) where

import Control.Applicative
import Control.Monad.Trans.State
import Control.Arrow (first)

import qualified Data.Map as M
import qualified Data.Text as T

import AO.Code
import AO.Compile
import AO.InnerDict

import ABC.Operators
import ABC.Resource
import ABC.Quote
import ABC.Simplify

-- | local cache of tokens to computed bytecode
type PreCompD  = M.Map ResourceToken [Op]

-- | cache of ciphertexts; secure hash is redundant here, just the
  secure hash of the ciphertext . We 'll assume there are no 384 - bit
-- collisions. If one is found, it is likely to cause an error 
-- downstream.
type SecStoreD = M.Map SecureHash CipherText 

data PCX md = PCX
    { pcx_dict :: M.Map Word (AO_Code, md)
    , pcx_pcw  :: M.Map Word ResourceToken
    , pcx_prcd :: PreCompD
    , pcx_secd :: SecStoreD
    }

-- | precompile all words for which `compile!word` is defined. Here,
-- the `compile!` prefix serves as a directive to the AO system.
-- 
-- Precompiled code is emitted as a map of abcResourceToken values 
  to Awelon bytecode , pre - simplified but otherwise unmodified .
preCompileDict :: AODict md -> (AODict md, (PreCompD, SecStoreD))
preCompileDict (AODict d) = toResult $ execState runPreCompile s0 where
    toResult s = (AODict (pcx_dict s), (pcx_prcd s, pcx_secd s))
    s0 = PCX d M.empty M.empty M.empty  

runPreCompile :: State (PCX md) ()
runPreCompile = gets (M.keys . pcx_dict) >>= mapM_ preComp

-- test if `compile!word` is defined, and that the word has not 
-- already been compiled (e.g. as part of compiling another word).
needPreComp :: Word -> State (PCX md) Bool
needPreComp w =
    let cw = T.pack "compile!" `T.append` w in
    get >>= \ s ->
    let bWantPreComp = M.member cw (pcx_dict s) in
    let bAlreadyDone = M.member w (pcx_pcw s) in 
    return (bWantPreComp && not bAlreadyDone)

-- precompile words when doing so is relevant.
preComp :: Word -> State (PCX md) ()
preComp w =
    needPreComp w >>= \ b ->
    if not b then return () else
    gets pcx_dict >>= \ d ->
    let code = fst (d M.! w) in
    simplify <$> (aoCodeToABC code) >>= \ ops ->
    makeResource storeSecD ops >>= \ tok ->
    updateDict w tok ops

  assuming no full 384 - bit secure hash collisions .
storeSecD :: SecureHash -> CipherText -> State (PCX md) ()
storeSecD k ct = modify $ \ s ->
    let secd' = M.insert k ct (pcx_secd s) in
    s { pcx_secd = secd' }

updateDict :: Word -> ResourceToken -> [Op] -> State (PCX md) ()
updateDict w tok ops = modify $ \ s ->
    let dict' = M.adjust (first $ const [AO_Tok tok]) w (pcx_dict s) in
    let pcw'  = M.insert w tok (pcx_pcw s) in
    let prcd' = M.insert tok ops (pcx_prcd s) in
    let secd' = pcx_secd s in
    PCX dict' pcw' prcd' secd'

  obtain partially precompiled bytecode for an ABC program
aoCodeToABC :: [AO_Action] -> State (PCX md) [Op]
aoCodeToABC (op:ops) = (++) <$> aoActionToABC op <*> aoCodeToABC ops
aoCodeToABC [] = return []

-- obtain bytecode, translating precompiled words to tokens
aoActionToABC :: AO_Action -> State (PCX md) [Op]
aoActionToABC (AO_Word w) =
    preComp w >> -- precompile word (if necessary)
    gets pcx_dict >>= \ d ->
    aoCodeToABC (fst (d M.! w))
aoActionToABC (AO_Block aoOps) = aoCodeToABC aoOps >>= \ ops -> return [BL ops, Op_l]
aoActionToABC (AO_Num r) = return $ quotes r [Op_l]
aoActionToABC (AO_Text txt) = return $ quotes (TL txt) [Op_l]
aoActionToABC (AO_ABC aOp) = return [aopToABC aOp]
aoActionToABC (AO_Tok tok) = return [Tok tok]

 | AO's 'inline everything' semantics are simple for reasoning, but
 not efficient. It makes poor reuse of memory, CPU cache, separate
 can be given a cryptographically unique resource token, which may


 Long term, the idea is that we should use machine learning to chop

 But this module is concerned with a short term solution. A subset
 replacing their definitions with the appropriate invocation. The
 
 The selection of words for compilation is based on convention. At
 this time, we simply compile every word `foo` for which there is
 a word `compile!foo` defined in the dictionary. This is rather
 ad-hoc, but it will do the job well enough for now.

 TODO: eventually, I need to support sensitivity concerns, i.e. to
 against confirmation attacks.

 | local cache of tokens to computed bytecode
 | cache of ciphertexts; secure hash is redundant here, just the
 collisions. If one is found, it is likely to cause an error 
 downstream.
 | precompile all words for which `compile!word` is defined. Here,
 the `compile!` prefix serves as a directive to the AO system.
 
 Precompiled code is emitted as a map of abcResourceToken values 
 test if `compile!word` is defined, and that the word has not 
 already been compiled (e.g. as part of compiling another word).
 precompile words when doing so is relevant.
 obtain bytecode, translating precompiled words to tokens
 precompile word (if necessary)
# LANGUAGE PatternGuards #

  compilation , and bandwidth . To mitigate this , AO systems leverage
  ABC 's separate compilation and linking model . Any sequence of ABC
  then be invoked to logically inline the associated ABC .
  See module  for more about the resource model .
  large ABC programs into highly reusable , near - optimal components .
  of words in the AO dictionary will be treated as ABC resources by
  ABC resources may truly be compiled separately for performance .
  compile sensitive ABC modules using an unguessable secret to guard
module AO.Precompile 
    ( preCompileDict
    , PreCompD, SecStoreD
    ) where

import Control.Applicative
import Control.Monad.Trans.State
import Control.Arrow (first)

import qualified Data.Map as M
import qualified Data.Text as T

import AO.Code
import AO.Compile
import AO.InnerDict

import ABC.Operators
import ABC.Resource
import ABC.Quote
import ABC.Simplify

type PreCompD  = M.Map ResourceToken [Op]

  secure hash of the ciphertext . We 'll assume there are no 384 - bit
type SecStoreD = M.Map SecureHash CipherText 

data PCX md = PCX
    { pcx_dict :: M.Map Word (AO_Code, md)
    , pcx_pcw  :: M.Map Word ResourceToken
    , pcx_prcd :: PreCompD
    , pcx_secd :: SecStoreD
    }

  to Awelon bytecode , pre - simplified but otherwise unmodified .
preCompileDict :: AODict md -> (AODict md, (PreCompD, SecStoreD))
preCompileDict (AODict d) = toResult $ execState runPreCompile s0 where
    toResult s = (AODict (pcx_dict s), (pcx_prcd s, pcx_secd s))
    s0 = PCX d M.empty M.empty M.empty  

runPreCompile :: State (PCX md) ()
runPreCompile = gets (M.keys . pcx_dict) >>= mapM_ preComp

needPreComp :: Word -> State (PCX md) Bool
needPreComp w =
    let cw = T.pack "compile!" `T.append` w in
    get >>= \ s ->
    let bWantPreComp = M.member cw (pcx_dict s) in
    let bAlreadyDone = M.member w (pcx_pcw s) in 
    return (bWantPreComp && not bAlreadyDone)

preComp :: Word -> State (PCX md) ()
preComp w =
    needPreComp w >>= \ b ->
    if not b then return () else
    gets pcx_dict >>= \ d ->
    let code = fst (d M.! w) in
    simplify <$> (aoCodeToABC code) >>= \ ops ->
    makeResource storeSecD ops >>= \ tok ->
    updateDict w tok ops

  assuming no full 384 - bit secure hash collisions .
storeSecD :: SecureHash -> CipherText -> State (PCX md) ()
storeSecD k ct = modify $ \ s ->
    let secd' = M.insert k ct (pcx_secd s) in
    s { pcx_secd = secd' }

updateDict :: Word -> ResourceToken -> [Op] -> State (PCX md) ()
updateDict w tok ops = modify $ \ s ->
    let dict' = M.adjust (first $ const [AO_Tok tok]) w (pcx_dict s) in
    let pcw'  = M.insert w tok (pcx_pcw s) in
    let prcd' = M.insert tok ops (pcx_prcd s) in
    let secd' = pcx_secd s in
    PCX dict' pcw' prcd' secd'

  obtain partially precompiled bytecode for an ABC program
aoCodeToABC :: [AO_Action] -> State (PCX md) [Op]
aoCodeToABC (op:ops) = (++) <$> aoActionToABC op <*> aoCodeToABC ops
aoCodeToABC [] = return []

aoActionToABC :: AO_Action -> State (PCX md) [Op]
aoActionToABC (AO_Word w) =
    gets pcx_dict >>= \ d ->
    aoCodeToABC (fst (d M.! w))
aoActionToABC (AO_Block aoOps) = aoCodeToABC aoOps >>= \ ops -> return [BL ops, Op_l]
aoActionToABC (AO_Num r) = return $ quotes r [Op_l]
aoActionToABC (AO_Text txt) = return $ quotes (TL txt) [Op_l]
aoActionToABC (AO_ABC aOp) = return [aopToABC aOp]
aoActionToABC (AO_Tok tok) = return [Tok tok]
