aos/tools/fof/IL/Paka/Builders.lhs
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

199 lines
7.4 KiB
Plaintext

%if false
Copyright (c) 2009, ETH Zurich.
All rights reserved.
This file is distributed under the terms in the attached LICENSE file.
If you do not find this file, copies can be found by writing to:
ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
%endif
%include polycode.fmt
%if false
> module IL.Paka.Builders where
> import Text.PrettyPrint.HughesPJ hiding (first)
> import qualified Data.Map as Map
> import Debug.Trace
> import PureExpressions
> import IL.Paka.Syntax
%endif
\section{Paka building blocks}
I'm particularly proud of the Paka code generation architecture. To
build a Paka term, we simply call some builders functions which are
chained up together with the |#| operator. These builders take care of
inserting the definitions in the right place in |PakaCode|,
|PakaIntra|, or sequentially extend the |ILPaka| code. Thanks to that
machinery, we don't have to explicitly build these data-structures, we
just call functions.
Hence, a builder is just putting a brick in the |PakaBuilding| wall:
> type PakaBuilding = (ILPaka -> ILPaka, PakaCode, PakaIntra)
That is, operations taking some arguments and extending a
|PakaBuilding| into a new one.
\subsection{Low-level machinery}
To give a feeling of ``sequential code'', the |#| operator is simply
an inversed composition operation:
> f # g = \x -> g (f x)
Using |#|, we will compose our builders with a sequential feeling.
Because most, if not all, operations modify one element of the
|PakaBuilding| triple, we define the following combinators:
> first :: (a -> b) -> (a, c, d) -> (b, c, d)
> first f (a,b,c) = (f a, b, c)
>
> second :: (a -> b) -> (c, a, d) -> (c, b, d)
> second f (a,b,c) = (a, f b, c)
>
> third :: (a -> b) -> (c, d, a) -> (c, d, b)
> third f (a,b,c) = (a,b,f c)
\subsection{Building |PakaCode|}
We can add new C includes:
> include :: String -> PakaBuilding -> PakaBuilding
> include id = second $ include' id
> where include' id globalEnv
> = case id `Map.lookup` incls of
> Nothing -> globalEnv { includes = Map.insert id decl incls }
> Just _ -> globalEnv
> where incls = includes globalEnv
> decl = text "#include" <+> text id
We can declare new C types:
> declare :: String -> Doc -> Doc -> PakaBuilding -> PakaBuilding
> declare id typ decl = second $ declare' id typ decl
> where declare' id typ decl globalEnv =
> case id `Map.lookup` typs of
> Nothing -> globalEnv { declarations = (id,decl) : decls,
> types = Map.insert id typ typs }
> Just _ -> globalEnv
> where decls = declarations globalEnv
> typs = types globalEnv
We can declare global variables:
> globalVar :: String -> Doc -> PakaBuilding -> PakaBuilding
> globalVar id def = second $ globalVar' id def
> where globalVar' id def globalEnv =
> case id `lookup` vars of
> Nothing -> globalEnv { globalVars = (id,def) : vars }
> Just _ -> globalEnv
> where vars = globalVars globalEnv
We can add function prototypes:
> prototype :: String -> Doc -> PakaBuilding -> PakaBuilding
> prototype id proto = second $ prototype' id proto
> where prototype' id proto globalEnv =
> case id `Map.lookup` protos of
> Nothing -> globalEnv { prototypes = Map.insert id proto protos }
> Just _ -> globalEnv
> where protos = prototypes globalEnv
And we can define new functions:
> function :: Doc -> Doc -> String -> Doc -> PakaIntra -> ILPaka -> PakaBuilding -> PakaBuilding
> function returnT attrs funName funArgs lEnv body =
> second $ function' returnT attrs funName funArgs lEnv body
> where function' returnT attrs funName funArgs lEnv body gEnv =
> case funName `Map.lookup` functions' of
> Nothing -> gEnv { functions = Map.insert funName (returnT, attrs, funName, funArgs, lEnv, body) functions' }
> Just _ -> gEnv
> where functions' = functions gEnv
\subsection{Building |PakaIntra|}
As for global variables in the |PakaCode|, we can add local variables
in the |PakaIntra| environment:
> localVar :: String -> Doc -> PakaBuilding -> PakaBuilding
> localVar id def = third $ localVar' id def
> where localVar' id def localEnv
> = case id `Map.lookup` vars of
> Nothing -> localEnv { localVars = Map.insert id def vars }
> Just _ -> localEnv
> where vars = localVars localEnv
And we can bring a constant in the |PakaIntra|:
> constant :: PureExpr -> PakaBuilding -> PakaBuilding
> constant e = third $ constant' e
> where constant' e lEnv = lEnv { expr = Just e }
\subsection{Building |ILPaka|}
Obviously, the serious stuff happens in |ILPaka|, or more precisely
|ILPaka -> ILPaka|: this code is seriously continuation-passing. The
plan is that we want to build a |ILPaka| value. However, we note that,
for instance, to build a |PStatement| value, we need to know the
remaining code. But we don't know it yet, as we are compiling it! So,
we return a continuation that waits for that uncompiled chunk and plug
it in the right place. Continuation-passing style, yay!
As an example of that technique in action, take a look at |instr| and
|assgn| below. Apart from that CPS detail, they are computationally
trivial, bringing their arguments in the right place of the
constructor and returning by calling the continuation.
> instr :: Term -> [PakaVarName] -> PakaBuilding -> PakaBuilding
> instr instruction vars = first $ instr' instruction vars
> where instr' instruction varNames k
> = \c ->
> k $ PStatement (PInstruction instruction varNames) c
>
> assgn :: PakaVarName -> Term -> [PakaVarName] -> PakaBuilding -> PakaBuilding
> assgn wVarName assgnmt rVarNames = first $ assgn' wVarName assgnmt rVarNames
> where assgn' wVarName assgnmt rVarNames k
> = \c ->
> k $ PStatement (PAssign wVarName assgnmt rVarNames) c
As you can expect, we need to stop ``continuating'' at some
point. This naturally fits with the role of closing terms:
> close :: PakaClosing -> PakaBuilding -> PakaBuilding
> close c = first $ close' c
> where close' c = \k _ -> k (PClosing c)
Similarly, the control-flow operators closes all their branches and
only continue downward:
> pif :: ILPaka -> PureExpr -> ILPaka -> ILPaka -> PakaBuilding -> PakaBuilding
> pif cond test ifTrue ifFalse = first $ pif' cond test ifTrue ifFalse
> where pif' cond test ifTrue ifFalse cont = \c ->
> cont $ PIf cond test ifTrue ifFalse c
>
> pwhile :: ILPaka -> PureExpr -> ILPaka -> PakaBuilding -> PakaBuilding
> pwhile cond test loop = first $ pwhile' cond test loop
> where pwhile' cond test loop cont = \c ->
> cont $ PWhile cond test loop c
>
> pdoWhile :: ILPaka -> ILPaka -> PureExpr -> PakaBuilding -> PakaBuilding
> pdoWhile loop cond test = first $ pdoWhile' loop cond test
> where pdoWhile' loop cond test cont = \c ->
> cont $ PDoWhile loop cond test c
>
> pswitch :: PureExpr -> [(PureExpr,ILPaka)] -> ILPaka -> PakaBuilding -> PakaBuilding
> pswitch test cases defaultCase = first $ pswitch' test cases defaultCase
> where pswitch' test cases defaultCase cont = \c ->
> cont $ PSwitch test cases defaultCase c