Skip to content

Commit bb8f13e

Browse files
committed
[feat] evaluate contracts ala CSL
1 parent 9e10c31 commit bb8f13e

20 files changed

+639
-71
lines changed

jl4-core/jl4-core.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ library
3131
hs-source-dirs: src
3232
build-depends:
3333
bytestring,
34+
placeholder,
3435
cassava,
3536
containers,
3637
data-default,
@@ -71,6 +72,7 @@ library
7172
L4.Evaluate.Value
7273
L4.Evaluate.ValueLazy
7374
L4.EvaluateLazy
75+
L4.EvaluateLazy.ContractFrame
7476
L4.ExactPrint
7577
L4.FindDefinition
7678
L4.HoverInfo

jl4-core/src/Base.hs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module Base (module X) where
44

55
import Control.DeepSeq as X
6+
import Debug.Trace as X
7+
import Control.Placeholder as X
68
import Control.Monad as X
79
import Control.Monad.Except as X
810
import Control.Monad.Identity as X

jl4-core/src/L4/Evaluate.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ evalDirective (StrictEval _ann expr) = do
355355
addEvalDirectiveResult expr v
356356
evalDirective (LazyEval _ann _expr) = pure ()
357357
evalDirective (Check _ _) = pure ()
358+
evalDirective (Contract {}) = pure ()
358359

359360
maximumStackSize :: Int
360361
maximumStackSize = 50
@@ -448,8 +449,7 @@ forwardExpr env !ss stack (AppNamed ann n nes (Just order)) =
448449
pushExprFrame env ss stack (App ann n es)
449450
forwardExpr env !ss stack (IfThenElse _ann e1 e2 e3) = do
450451
pushEvalFrame env ss (IfThenElse1 e2 e3 env stack) e1
451-
forwardExpr _env !ss stack (Regulative _ann _e1 _e2 _me3 _me4) = do
452-
backwardExpr ss stack ValRegulative -- TODO
452+
forwardExpr _env !_ss stack Regulative {} = exception (RuntimeTypeError "strict evaluation of contracts is currently not supported") stack -- TODO: probably wanna change this
453453
forwardExpr env !ss stack (Consider _ann e branches) = do
454454
pushEvalFrame env ss (Consider1 branches env stack) e
455455
forwardExpr _env !ss stack (Lit _ann lit) = do

jl4-core/src/L4/Evaluate/ValueLazy.hs

+11-4
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,19 @@ data Value a =
4343
| ValNil
4444
| ValCons a a
4545
| ValClosure (GivenSig Resolved) (Expr Resolved) Environment
46+
| ValObligation a a (Maybe a) a
4647
| ValUnappliedConstructor Resolved
4748
| ValConstructor Resolved [a]
4849
| ValAssumed Resolved
4950
| ValEnvironment Environment
50-
| ValRegulative -- TODO
51-
deriving stock Show
51+
| ValBreached (ReasonForBreach a)
52+
deriving stock (Show, Functor, Foldable, Traversable)
53+
54+
data ReasonForBreach a
55+
= DeadlineMissed (Value a) (Value a) (Value a) Int
56+
| NoProgress (Value a) (Value a) (Maybe (Value a))
57+
deriving stock (Generic, Show, Functor, Foldable, Traversable)
58+
deriving anyclass NFData
5259

5360
-- | This is a non-standard instance because environments can be recursive, hence we must
5461
-- not actually force the environments ...
@@ -58,11 +65,11 @@ instance NFData a => NFData (Value a) where
5865
rnf (ValNumber i) = rnf i
5966
rnf (ValString t) = rnf t
6067
rnf ValNil = ()
61-
rnf ValRegulative = ()
6268
rnf (ValCons r1 r2) = rnf r1 `seq` rnf r2
6369
rnf (ValClosure given expr env) = env `seq` rnf given `seq` rnf expr
6470
rnf (ValUnappliedConstructor r) = rnf r
6571
rnf (ValConstructor r vs) = rnf r `seq` rnf vs
6672
rnf (ValAssumed r) = rnf r
6773
rnf (ValEnvironment env) = env `seq` ()
68-
74+
rnf (ValBreached ev) = rnf ev `seq` ()
75+
rnf (ValObligation p a t f) = p `deepseq` a `deepseq` t `deepseq` f `deepseq` ()

jl4-core/src/L4/EvaluateLazy.hs

+170-21
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import L4.Parser.SrcSpan (SrcRange)
1919
import L4.Print
2020
import L4.Syntax
2121
import qualified L4.TypeCheck as TypeCheck
22-
23-
import Data.Either
22+
import L4.EvaluateLazy.ContractFrame
2423

2524
newtype Eval a = MkEval (EvalState -> IO (Either EvalException a))
2625
deriving (Functor, Applicative, Monad, MonadError EvalException, MonadReader EvalState, MonadIO)
@@ -121,7 +120,7 @@ emptyStack = MkStack 0 []
121120
data Frame =
122121
BinOp1 BinOp {- -} (Expr Resolved) Environment
123122
| BinOp2 BinOp WHNF {- -}
124-
| App1 {- -} [Expr Resolved] Environment
123+
| App1 {- -} [Reference]
125124
| IfThenElse1 {- -} (Expr Resolved) (Expr Resolved) Environment
126125
| ConsiderWhen1 Reference {- -} (Expr Resolved) [Branch Resolved] Environment
127126
| PatNil0
@@ -134,6 +133,7 @@ data Frame =
134133
| EqConstructor2 WHNF {- -} [(Reference, Reference)]
135134
| EqConstructor3 {- -} [(Reference, Reference)]
136135
| UpdateThunk Reference
136+
| ContractFrame ContractFrame
137137
deriving stock Show
138138

139139
step :: Eval Int
@@ -353,7 +353,8 @@ forwardExpr env (Lam _ann givens e) =
353353
forwardExpr env (App _ann n []) =
354354
expectTerm env n >>= evalRef
355355
forwardExpr env (App _ann n es@(_ : _)) = do
356-
pushFrame (App1 es env)
356+
rs <- traverse (`allocate_` env) es
357+
pushFrame (App1 rs)
357358
forwardExpr env (Var emptyAnno n)
358359
forwardExpr env (AppNamed ann n [] _) =
359360
forwardExpr env (App ann n [])
@@ -382,7 +383,12 @@ forwardExpr env (Where _ann e ds) = do
382383
env' <- evalRecLocalDecls env ds
383384
let combinedEnv = Map.union env' env
384385
forwardExpr combinedEnv e
385-
forwardExpr env (Regulative {}) = do undefined -- TODO:
386+
forwardExpr env (Regulative _ann (MkObligation _ party action due followup)) = do
387+
partyR <- allocate_ party env
388+
actionR <- allocate_ action env
389+
dueR <- traverse (`allocate_` env) due
390+
followupR <- allocate_ (fromMaybe fulfilExpr followup) env
391+
backward (ValObligation partyR actionR dueR followupR)
386392

387393
backward :: WHNF -> Eval WHNF
388394
backward val = withPoppedFrame $ \ case
@@ -392,14 +398,20 @@ backward val = withPoppedFrame $ \ case
392398
forwardExpr env e2
393399
Just (BinOp2 binOp val1) -> do
394400
runBinOp binOp val1 val
395-
Just f@(App1 es env) -> do
401+
Just f@(App1 rs) -> do
396402
case val of
397403
ValClosure givens e env' -> do
398-
env'' <- matchGivens givens f env es
404+
env'' <- matchGivens givens f rs
399405
forwardExpr (Map.union env'' env') e
400406
ValUnappliedConstructor r -> do
401-
refs <- traverse (flip allocate_ env) es
402-
backward (ValConstructor r refs)
407+
backward (ValConstructor r rs)
408+
ValObligation party act due followup -> do
409+
(evs, time) <- case rs of
410+
[r, t] -> pure (r, t)
411+
rs' -> internalException $ RuntimeTypeError $ "expected a list of events, but found: " <> foldMap prettyLayout rs'
412+
pushFrame (ContractFrame (Contract1 (C1Frame (Right party) (Right act) due followup time)))
413+
evalRef evs
414+
v@(ValConstructor r []) | r `sameResolved` TypeCheck.fulfilRef -> pure v
403415
ValAssumed r ->
404416
stuckOnAssumed r -- TODO: we can do better here
405417
res -> internalException (RuntimeTypeError $ "expected a function but found: " <> prettyLayout res)
@@ -490,15 +502,97 @@ backward val = withPoppedFrame $ \ case
490502
Just (UpdateThunk rf) -> do
491503
updateThunkToWHNF rf val
492504
backward val
505+
Just (ContractFrame cFrame) -> backwardConctractFrame val cFrame
493506

494-
matchGivens :: GivenSig Resolved -> Frame -> Environment -> [Expr Resolved] -> Eval Environment
495-
matchGivens (MkGivenSig _ann otns) f env es = do
496-
let
497-
(_tyvars, others) = partitionEithers (TypeCheck.isQuantifier <$> otns)
498-
if length others == length es
507+
backwardConctractFrame :: Value Reference -> ContractFrame -> Eval WHNF
508+
backwardConctractFrame val = \case
509+
Contract1 (C1Frame party act due followup time) -> do
510+
case val of
511+
ValCons e es -> do
512+
pushCFrame (Contract2 (C2Frame party act due followup es time))
513+
evalRef e
514+
ValNil -> do
515+
party' <- evalRef' party
516+
act' <- evalRef' act
517+
due' <- traverse evalRef due
518+
backward (ValBreached (NoProgress party' act' due'))
519+
_ -> internalException (RuntimeTypeError $ "expected LIST EVENT but found: " <> prettyLayout val <> " when scrutinizing regulative events")
520+
Contract2 (C2Frame party act due followup es time) -> do
521+
(p, a, t) <- case val of
522+
ValConstructor n [p, a, t] | n `sameResolved` TypeCheck.eventRef -> pure (p, a, t)
523+
_ -> internalException (RuntimeTypeError $ "expected an EVENT but found: " <> prettyLayout val <> " when scrutinizing a regulative event")
524+
pushCFrame (Contract3 (C3Frame act due followup p a t es time))
525+
evalRef' party
526+
Contract3 (C3Frame act due followup p a t es time) -> do
527+
pushCFrame (Contract4 (C4Frame val act due followup a t es time))
528+
evalRef p
529+
Contract4 (C4Frame party act due followup a t es time) -> do
530+
sameParty <- runBinOpEquals party val
531+
case boolView sameParty of
532+
Nothing -> internalException (RuntimeTypeError $ "expected BOOLEAN but found: " <> prettyLayout sameParty)
533+
Just True -> do
534+
pushCFrame (Contract5 (C5Frame party due followup val a t es time))
535+
evalRef' act
536+
Just False -> continueWithNextEvent (Left party) act due followup es time
537+
Contract5 (C5Frame party due followup p a t es time) -> do
538+
pushCFrame (Contract6 (C6Frame party val due followup p t es time))
539+
evalRef a
540+
Contract6 (C6Frame party act due followup p t es time) -> do
541+
sameAct <- runBinOpEquals act val
542+
case boolView sameAct of
543+
Nothing -> internalException (RuntimeTypeError $ "expected BOOLEAN but found: " <> prettyLayout sameAct)
544+
Just True -> case due of
545+
Just due' -> do
546+
pushCFrame (Contract7 (C7Frame followup p val t es time))
547+
evalRef due'
548+
Nothing -> continueWithFollowup followup es time
549+
Just False -> continueWithNextEvent (Left party) (Left act) due followup es time
550+
Contract7 (C7Frame followup p a t es time) -> do
551+
pushCFrame (Contract8 (C8Frame val followup p a es time))
552+
evalRef t
553+
Contract8 (C8Frame due followup p a es time) -> do
554+
pushCFrame (Contract9 (C9Frame due followup p a val es))
555+
evalRef time
556+
Contract9 (C9Frame due followup p a t es) -> do
557+
let assertTime = \case
558+
ValNumber i -> pure i
559+
v -> internalException (RuntimeTypeError $ "expected a NUMBER but got: " <> prettyLayout v)
560+
stamp <- assertTime t
561+
due' <- assertTime due
562+
time <- assertTime val
563+
-- TODO: this is not too nice, but not wanting this would require to change `App1` to take MaybeEvaluated's
564+
timeR <- allocateValue val
565+
let deadline = time + due'
566+
if stamp > deadline
567+
then backward (ValBreached (DeadlineMissed p a t deadline))
568+
else continueWithFollowup followup es timeR
569+
570+
where
571+
continueWithNextEvent :: MaybeEvaluated -> MaybeEvaluated -> Maybe Reference -> Reference -> Reference -> Reference -> Eval WHNF
572+
continueWithNextEvent party act due followup es time = do
573+
pushCFrame (Contract1 (C1Frame party act due followup time))
574+
evalRef es
575+
576+
pushCFrame = pushFrame . ContractFrame
577+
578+
continueWithFollowup :: Reference -> Reference -> Reference -> Eval WHNF
579+
continueWithFollowup followup es time = do
580+
pushFrame (App1 [es, time])
581+
evalRef followup
582+
583+
evalRef' :: MaybeEvaluated -> Eval WHNF
584+
evalRef' = either pure evalRef
585+
586+
matchGivens :: GivenSig Resolved -> Frame -> [Reference] -> Eval Environment
587+
matchGivens (MkGivenSig _ann otns) f es = do
588+
let others = foldMap (either (const []) (\x -> [fst x]) . TypeCheck.isQuantifier) otns
589+
matchGivens' others f es
590+
591+
matchGivens' :: [Resolved] -> Frame -> [Reference] -> Eval (Map Unique Reference)
592+
matchGivens' ns f rs = do
593+
if length ns == length rs
499594
then do
500-
refs <- traverse (flip allocate_ env) es
501-
pure $ Map.fromList (zipWith (\ (r, _) v -> (getUnique r, v)) others refs)
595+
pure $ Map.fromList (zipWith (\ r v -> (getUnique r, v)) ns rs)
502596
else do
503597
pushFrame f -- provides better error context
504598
internalException (RuntimeTypeError "given signatures' values' lengths do not match")
@@ -697,6 +791,14 @@ evalDirective _env (StrictEval _ann _expr) =
697791
pure []
698792
evalDirective _env (Check _ann _expr) =
699793
pure []
794+
evalDirective env (Contract ann expr evs) = do
795+
evalDirective env (LazyEval ann (contractToEvalDirective expr evs))
796+
797+
contractToEvalDirective :: Expr Resolved -> [Event Resolved] -> Expr Resolved
798+
contractToEvalDirective contract evs = App emptyAnno TypeCheck.evalContractRef [contract, evListExpr]
799+
where
800+
evListExpr = List emptyAnno $ map eventExpr evs
801+
eventExpr (MkEvent ann party act timestamp) = App ann TypeCheck.eventRef [party, act, timestamp]
700802

701803
evalLocalDecl :: Environment -> LocalDecl Resolved -> Eval ()
702804
evalLocalDecl env (LocalDecide _ann decide) =
@@ -778,17 +880,45 @@ trueExpr = App emptyAnno TypeCheck.trueRef []
778880
trueVal :: WHNF
779881
trueVal = ValConstructor TypeCheck.trueRef []
780882

883+
fulfilExpr :: Expr Resolved
884+
fulfilExpr = App emptyAnno TypeCheck.fulfilRef []
885+
886+
fulfilVal :: Value a
887+
fulfilVal = ValConstructor TypeCheck.fulfilRef []
888+
889+
-- \d e f. d e f
890+
evalContractVal :: Value a
891+
evalContractVal = ValClosure
892+
(MkGivenSig emptyAnno
893+
[ MkOptionallyTypedName emptyAnno TypeCheck.dDef Nothing
894+
, MkOptionallyTypedName emptyAnno TypeCheck.eDef Nothing
895+
, MkOptionallyTypedName emptyAnno TypeCheck.f'Def Nothing
896+
]
897+
)
898+
(App emptyAnno TypeCheck.dRef [App emptyAnno TypeCheck.eRef [], App emptyAnno TypeCheck.f'Ref []])
899+
emptyEnvironment
900+
901+
-- EVENT :: party -> act -> Number -> EVENT party act
902+
eventCVal :: Value a
903+
eventCVal = ValUnappliedConstructor TypeCheck.eventRef
904+
781905
-- The initial environment has to be built by pre-allocation.
782906
initialEnvironment :: Eval Environment
783907
initialEnvironment = do
784908
falseRef <- allocateValue falseVal
785909
trueRef <- allocateValue trueVal
786910
nilRef <- allocateValue ValNil
911+
evalContractRef <- allocateValue evalContractVal
912+
eventCRef <- allocateValue eventCVal
913+
fulfilRef <- allocateValue fulfilVal
787914
pure $
788915
Map.fromList
789916
[ (TypeCheck.falseUnique, falseRef)
790917
, (TypeCheck.trueUnique, trueRef)
791918
, (TypeCheck.emptyUnique, nilRef)
919+
, (TypeCheck.evalContractUnique, evalContractRef)
920+
, (TypeCheck.eventCUnique, eventCRef)
921+
, (TypeCheck.fulfilUnique, fulfilRef)
792922
]
793923

794924
-- TODO: This currently allocates the initial environment once per module.
@@ -827,13 +957,32 @@ nfAux d (ValCons r1 r2) = do
827957
v2 <- evalRef r2 >>= nfAux (d - 1)
828958
pure (MkNF (ValCons v1 v2))
829959
nfAux _d (ValClosure givens e env) = pure (MkNF (ValClosure givens e env))
960+
nfAux d (ValObligation party act due followup) = do
961+
party' <- (nfAux (d - 1) <=< evalRef) party
962+
act' <- (nfAux (d - 1) <=< evalRef) act
963+
due' <- traverse (nfAux (d - 1) <=< evalRef) due
964+
followup' <- (nfAux (d - 1) <=< evalRef) followup
965+
pure (MkNF (ValObligation party' act' due' followup'))
830966
nfAux _d (ValUnappliedConstructor n) = pure (MkNF (ValUnappliedConstructor n))
831967
nfAux d (ValConstructor n rs) = do
832968
vs <- traverse (\ r -> evalRef r >>= nfAux (d - 1)) rs
833969
pure (MkNF (ValConstructor n vs))
834970
nfAux _d (ValAssumed n) = pure (MkNF (ValAssumed n))
835971
nfAux _d (ValEnvironment env) = pure (MkNF (ValEnvironment env))
836-
nfAux _d (ValRegulative) = pure (MkNF ValRegulative)
972+
nfAux d (ValBreached r') = do
973+
let evalAndNF = traverse $ nfAux (d - 1) <=< evalRef
974+
r <- case r' of
975+
NoProgress party act timestamp -> do
976+
party' <- evalAndNF party
977+
act' <- evalAndNF act
978+
timestamp' <- traverse evalAndNF timestamp
979+
pure (NoProgress party' act' timestamp')
980+
DeadlineMissed party act timestamp deadline -> do
981+
party' <- evalAndNF party
982+
act' <- evalAndNF act
983+
timestamp' <- evalAndNF timestamp
984+
pure (DeadlineMissed party' act' timestamp' deadline)
985+
pure (MkNF (ValBreached r))
837986

838987
-- | Main entry point.
839988
--
@@ -847,9 +996,9 @@ execEvalModuleWithEnv :: Environment -> Module Resolved -> IO (Environment, [Eva
847996
execEvalModuleWithEnv env m@(MkModule _ moduleUri _) = do
848997
case evalModuleAndDirectives env m of
849998
MkEval f -> do
850-
initialStack <- newIORef emptyStack
851-
initialSupply <- newIORef 0
852-
r <- f (MkEvalState moduleUri initialStack initialSupply)
999+
stack <- newIORef emptyStack
1000+
supply <- newIORef 0
1001+
r <- f MkEvalState {moduleUri, stack, supply}
8531002
case r of
8541003
Left _exc -> do
8551004
-- exceptions at the top-level are unusual; after all, we don't actually
@@ -868,7 +1017,7 @@ data EvalDirective =
8681017

8691018
data EvalDirectiveResult =
8701019
MkEvalDirectiveResult
871-
{ range :: !SrcRange -- ^ of the (L)EVAL directive
1020+
{ range :: !SrcRange -- ^ of the (L)EVAL / CONTRACT directive
8721021
, result :: Either EvalException NF
8731022
}
8741023
deriving stock (Generic, Show)

0 commit comments

Comments
 (0)