ContractDesugar.hs (4494B)
1 {-# LANGUAGE LambdaCase #-} 2 3 module Frontend.ContractDesugar 4 ( desugarContracts 5 , viewExprToAst 6 ) where 7 8 import Research 9 10 -- | Convert source-level contract annotations into runtime boundary checks. 11 -- 12 -- Named binder annotations (e.g. @x@nat?) wrap each argument as it is 13 -- received and the result before it is returned. 14 -- 15 -- Phantom annotations (e.g. @nat? on a point-free definition) are turned 16 -- into a fresh local raw value plus a wrapper definition that uses named 17 -- binder annotations. The raw value is bound with a local 'let' so that 18 -- fixed points (such as definitions built with 'y') are shared rather than 19 -- recreated on every call. The wrapper only needs 'withContract' and 20 -- 'matchResult' from the contract library. 21 desugarContracts :: [TricuAST] -> [TricuAST] 22 desugarContracts asts = concatMap desugarTopItem asts 23 where 24 desugarTopItem (SDefAnn name args ret body) = desugarDefAnn name args ret body 25 desugarTopItem other = [other] 26 27 -- | Fresh internal name for the raw, contract-free helper introduced by 28 -- phantom annotations. It is bound locally with 'SLet' so it never escapes 29 -- into the final environment. 30 rawNameFor :: String -> String 31 rawNameFor name = "_" ++ name ++ "_raw" 32 33 desugarDefAnn :: String -> [DefArg] -> Maybe ViewExpr -> TricuAST -> [TricuAST] 34 desugarDefAnn name args ret body 35 | all isPhantom args = 36 let argContracts = map getPhantom args 37 rawNm = rawNameFor name 38 rawVar = SVar rawNm Nothing 39 argNames = take (length args) ["x","y","z"] 40 newArgs = zipWith DefBinder argNames (map Just argContracts) 41 wrappedBody = foldl SApp rawVar (map (\n -> SVar n Nothing) argNames) 42 wrapper = wrapArgs newArgs (wrapReturn ret wrappedBody) 43 in [ SDef name [] (SLet rawNm body wrapper) ] 44 | otherwise = [ SDef name [] (wrapArgs args body') ] 45 where 46 body' = wrapReturn ret body 47 48 isPhantom (DefPhantom _) = True 49 isPhantom _ = False 50 51 getPhantom (DefPhantom c) = c 52 getPhantom _ = error "expected phantom annotation" 53 54 -- | Build: matchResult onFail (\value _ -> body) result 55 bindResult result valueName body onFail = 56 matchResultE 57 onFail 58 (SLambda [valueName, "_"] body) 59 result 60 61 -- | Build: matchResult (\msg _ -> err msg t) (\r _ -> r) result 62 returnResult result = 63 matchResultE 64 (SLambda ["msg", "_"] errResult) 65 (SLambda ["r", "_"] (SVar "r" Nothing)) 66 result 67 68 -- | Build: \msg _ -> \_ -> err msg t 69 absorbErr = 70 SLambda ["msg", "_"] (SLambda ["_"] errResult) 71 72 errResult = SApp (SApp (SVar "err" Nothing) (SVar "msg" Nothing)) TLeaf 73 74 wrapReturn Nothing b = b 75 wrapReturn (Just c) b = returnResult (withContractE (viewExprToAst c) b) 76 77 wrapArgs [] b = b 78 wrapArgs (DefBinder nm Nothing : rest) b = SLambda [nm] (wrapArgs rest b) 79 wrapArgs (DefBinder nm (Just c) : rest) b = 80 let onFail = if null rest then SLambda ["msg", "_"] errResult else absorbErr 81 in SLambda [nm] $ 82 bindResult 83 (withContractE (viewExprToAst c) (SVar nm Nothing)) 84 nm 85 (wrapArgs rest b) 86 onFail 87 wrapArgs (DefPhantom _ : _) _ = 88 error "phantom contract arguments are not yet supported by the frontend" 89 90 -- | Turn a source annotation expression into an ordinary AST expression. 91 -- Contract annotations are written with the same surface syntax as terms, 92 -- so the mapping is mostly structural. 93 viewExprToAst :: ViewExpr -> TricuAST 94 viewExprToAst = \case 95 VEName s -> SVar s Nothing 96 VEVar s -> SVar s Nothing 97 VEInt i -> SInt i 98 VEString s -> SStr s 99 VEList es -> SList (map viewExprToAst es) 100 VEApp f a -> SApp (viewExprToAst f) (viewExprToAst a) 101 VERaw s -> SStr s 102 VEVarId _ -> error "view variable ids are not supported by the frontend" 103 VEForall _ _ -> error "forall annotations are not supported by the frontend" 104 VEExists _ _ -> error "exists annotations are not supported by the frontend" 105 106 -- | Build an application of 'withContract' from the contract library. 107 withContractE :: TricuAST -> TricuAST -> TricuAST 108 withContractE contract value = 109 SApp (SApp (SVar "withContract" Nothing) contract) value 110 111 -- | Build an application of 'matchResult' from the contract library. 112 matchResultE :: TricuAST -> TricuAST -> TricuAST -> TricuAST 113 matchResultE errCase okCase result = 114 SApp 115 (SApp 116 (SApp (SVar "matchResult" Nothing) errCase) 117 okCase) 118 result