Skip to content

Commit 776d7bf

Browse files
committed
extend type environment; do subs and functions
1 parent 064317d commit 776d7bf

1 file changed

Lines changed: 173 additions & 103 deletions

File tree

src/Language/Fortran/Generate.hs

Lines changed: 173 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Language.Fortran.Generate where
44
import Language.Fortran.AST
55
import Language.Fortran.AST.Literal
66
import Language.Fortran.AST.Literal.Real
7-
import Test.QuickCheck
7+
import Test.QuickCheck hiding (Fun)
88

99
import Language.Fortran.Util.Position
1010
import Language.Fortran.PrettyPrint
@@ -77,8 +77,25 @@ nullSpan = SrcSpan initPosition initPosition
7777
-- Stateful generation
7878
--------------------------------------------------------------------------------
7979

80-
-- | Environment mapping variable names to their declared types.
81-
type Env = Map Name (TypeSpec A0)
80+
-- | Typing Environment for the code generator
81+
data Env = Env
82+
{ localVariables :: Map Name (TypeSpec A0)
83+
, functions :: Map Name ([TypeSpec A0], TypeSpec A0)
84+
, subroutines :: Map Name [TypeSpec A0]
85+
}
86+
87+
data VarType = Var | Sub | Fun
88+
89+
emptyEnv :: Env
90+
emptyEnv = Env { localVariables = Map.empty
91+
, functions = Map.empty
92+
, subroutines = Map.empty
93+
}
94+
95+
instance Show VarType where
96+
show Var = "var"
97+
show Sub = "sub"
98+
show Fun = "fun"
8299

83100
-- | Stateful generator: a 'Gen' action that can read/write an 'Env'.
84101
type GenM a = StateT Env Gen a
@@ -91,149 +108,202 @@ liftGen = lift
91108
-- typing environment.
92109
--
93110
-- The default implementation lifts 'arbitrary', so any type with an
94-
-- 'Arbitrary' instance gets an 'ArbitraryCtxt' instance for free:
95-
--
96-
-- > instance ArbitraryCtxt BaseType -- uses default
97-
--
98-
-- Override for types whose generation depends on the environment:
99-
--
100-
-- > instance ArbitraryCtxt (Statement A0) where
101-
-- > arbitraryCtxt = genDecl
102-
class ArbitraryCtxt a where
103-
arbitraryCtxt :: GenM a
104-
default arbitraryCtxt :: Arbitrary a => GenM a
105-
arbitraryCtxt = liftGen arbitrary
111+
-- 'Arbitrary' instance gets an 'ArbitraryInCtxt' instance for free.
112+
113+
class ArbitraryInCtxt a where
114+
arbitraryInCtxt :: GenM a
115+
default arbitraryInCtxt :: Arbitrary a => GenM a
116+
arbitraryInCtxt = liftGen arbitrary
106117

107-
instance ArbitraryCtxt BaseType
108-
instance ArbitraryCtxt (TypeSpec A0)
118+
instance ArbitraryInCtxt BaseType
119+
instance ArbitraryInCtxt (TypeSpec A0)
109120

110121
-- | Generate a fresh variable name based on the current environment size.
111-
freshName :: GenM Name
112-
freshName = do
122+
freshName :: VarType -> GenM Name
123+
freshName varType = do
113124
env <- get
114-
pure $ "var" ++ show (Map.size env)
115-
125+
let number =
126+
case varType of
127+
Var -> Map.size (localVariables env)
128+
Sub -> Map.size (subroutines env)
129+
Fun -> Map.size (functions env)
130+
return $ show varType ++ show number
131+
116132
--------------------------------------------------------------------------------
117133
-- Generate typing context and declarations
118134
--------------------------------------------------------------------------------
119135

120-
-- | Generate one declaration statement, adding the variable to the environment.
136+
-- | Generate one variable declaration statement, adding the variable to the environment.
121137
-- The flag controls whether the declaration carries an initializer
122138
-- (dummy arguments must not be initialised).
123-
genDecl :: Bool -> GenM (Statement A0)
139+
genDecl :: Bool -> GenM (TypeSpec A0, Statement A0)
124140
genDecl initialise = do
125-
name <- freshName
126-
typeSpec <- arbitraryCtxt
141+
name <- freshName Var
142+
typeSpec <- arbitraryInCtxt
127143
initialExpr <- if initialise
128144
then Just <$> genTypedValue typeSpec
129145
else pure Nothing
130146
let
131147
varExpr = ExpValue () nullSpan (ValVariable name)
132148
decl = Declarator () nullSpan varExpr ScalarDecl Nothing initialExpr
133149
declList = AList () nullSpan [decl]
134-
modify (Map.insert name typeSpec)
135-
pure $ StDeclaration () nullSpan typeSpec Nothing declList
150+
modify (\st -> st { localVariables = Map.insert name typeSpec (localVariables st) } )
151+
pure (typeSpec, StDeclaration () nullSpan typeSpec Nothing declList)
136152

137153
-- | Generate @n@ declarations, building up the environment as we go.
138-
genDecls :: Bool -> Int -> GenM [Statement A0]
154+
genDecls :: Bool -> Int -> GenM [(TypeSpec A0, Statement A0)]
139155
genDecls initialise n = replicateM n (genDecl initialise)
140156

141157
-- | Generate a program unit with a growing set of declarations.
142158
instance Arbitrary (ProgramUnit A0) where
143159
arbitrary = sized $ \sz -> do
160+
-- Generate some other subroutines and functions
161+
(procs, env) <- runStateT genProcedures emptyEnv
162+
163+
-- Generate some top-level declarations for the main program
144164
-- Uses QuickCheck's 'sized' so the number of declarations scales with test size.
145165
let numDecls = max 1 (sz `div` 5)
146-
-- Generate some declarations
147-
(decls, env) <- runStateT (genDecls True numDecls) Map.empty
148-
-- env is now available for generating expressions / further statements
149-
let declBlocks = map (\s -> BlStatement () nullSpan Nothing s) decls
150-
-- Generate some other subroutines and functions
151-
procs <- evalStateT genProcedures env
152-
-- Generate main program unit statements
153-
-- TODO: these need access to the `procs`
154-
computeBlocks <- evalStateT genBodyBlocks env
155-
let blocks = declBlocks ++ computeBlocks ++ (printAllEnd env)
166+
(decls, env) <- runStateT (genDecls True numDecls) env
167+
let declBlocks = map (\(_, s) -> BlStatement () nullSpan Nothing s) decls
168+
-- Generate main program unit's statements
169+
topLevelBlocks <- evalStateT genBodyBlocks env
170+
let blocks = declBlocks ++ topLevelBlocks ++ (printAllEnd env)
156171
let name = "generated"
157172
pure $ PUMain () nullSpan (Just name) blocks (Just procs)
158173
where
159174
-- print out everything in the environment at the end
160175
printAllEnd env =
161-
[ BlStatement () nullSpan Nothing (StPrint () nullSpan (ExpValue () nullSpan ValStar) (fromList' () (map (\n -> ExpValue () nullSpan (ValVariable n)) (Map.keys env)))) ]
176+
[ BlStatement () nullSpan Nothing (StPrint () nullSpan (ExpValue () nullSpan ValStar) (fromList' () (map (\n -> ExpValue () nullSpan (ValVariable n)) (Map.keys (localVariables env))))) ]
162177

163-
instance ArbitraryCtxt (Statement A0) where
164-
-- Bias assignments over print statements
165-
arbitraryCtxt = oneofCtxt (printer : replicate 3 assignment)
178+
instance ArbitraryInCtxt (Statement A0) where
179+
-- Pick
180+
arbitraryInCtxt = oneofCtxt ([printer] ++ replicate 2 subroutine ++ replicate 3 assignment)
166181
where
167-
assignment :: GenM (Statement A0)
168-
assignment = do
169-
(lvar, typ) <- pickVar
170-
expr <- genTypedExpression typ
171-
pure $ StExpressionAssign () nullSpan (ExpValue () nullSpan (ValVariable lvar)) expr
172-
173-
printer :: GenM (Statement A0)
174-
printer = do
175-
(name, _) <- pickVar
176-
let expr = ExpValue () nullSpan (ValVariable name)
177-
pure $ StPrint () nullSpan (ExpValue () nullSpan ValStar) (fromList' () [expr])
178-
179-
-- | Generate the compute statements of a body as blocks, from the current
180-
-- environment.
182+
assignment :: GenM (Statement A0)
183+
assignment = do
184+
(lvar, typ) <- pickVar
185+
expr <- genTypedExpression typ
186+
pure $ StExpressionAssign () nullSpan (ExpValue () nullSpan (ValVariable lvar)) expr
187+
188+
subroutine :: GenM (Statement A0)
189+
subroutine = do
190+
-- Pick a subroutine; fall back to assignment if none exist yet
191+
env <- get
192+
if Map.null (subroutines env)
193+
then assignment
194+
else do
195+
(subName, subArgTypes) <- liftGen $ elements (Map.toList $ subroutines env)
196+
-- Generate expressions for each argument
197+
argExprs <- mapM genTypedExpression subArgTypes
198+
let subExpr = ExpValue () nullSpan (ValVariable subName)
199+
argList = AList () nullSpan (map (Argument () nullSpan Nothing . ArgExpr) argExprs)
200+
pure $ StCall () nullSpan subExpr argList
201+
202+
printer :: GenM (Statement A0)
203+
printer = do
204+
(name, _) <- pickVar
205+
let expr = ExpValue () nullSpan (ValVariable name)
206+
pure $ StPrint () nullSpan (ExpValue () nullSpan ValStar) (fromList' () [expr])
207+
208+
-- | Generate the statements of a body as blocks
181209
genBodyBlocks :: GenM [Block A0]
182210
genBodyBlocks = do
183-
env <- get
184-
if Map.null env
185-
-- Statements need at least one variable to refer to
186-
then pure []
187-
else do
188-
sz <- liftGen getSize
189-
n <- liftGen $ choose (0, sz)
190-
statements <- replicateM n (arbitraryCtxt :: GenM (Statement A0))
191-
pure $ map (BlStatement () nullSpan Nothing) statements
192-
211+
env <- get
212+
-- Statements need at least one variable to refer to
213+
if Map.null (localVariables env)
214+
then pure []
215+
else do
216+
sz <- liftGen getSize
217+
n <- liftGen $ choose (0, sz)
218+
statements <- replicateM n (arbitraryInCtxt :: GenM (Statement A0))
219+
pure $ map (BlStatement () nullSpan Nothing) statements
220+
221+
-- Generate a list of procedures (subroutines or functions)
193222
genProcedures :: GenM [ProgramUnit A0]
194223
genProcedures = do
195224
sz <- liftGen getSize
196225
numProcs <- liftGen $ choose (1, max 1 (sz `div` 5))
197-
-- Number the procedures so their names are unique
198-
mapM (\i -> genProcedure ("generated_subroutine" ++ show i)) [1 .. numProcs]
226+
-- Names are made unique by freshName
227+
replicateM numProcs genProcedure
199228

200229
-- Synthesise some procedures (subroutines or functions), which
201230
-- can make use of a global environment passed to it.
202-
genProcedure :: Name -> GenM (ProgramUnit A0)
203-
genProcedure procName = do
204-
annotation <- liftGen $ arbitrary
205-
sz <- liftGen getSize
206-
numArgs <- liftGen $ choose (0, max 1 (sz `div` 5))
207-
-- Generate the parameters in a fresh local environment (own scope),
208-
-- reusing genDecls; the resulting env gives us the argument names.
209-
(argDecls, localEnv) <- liftGen $ runStateT (genDecls False numArgs) Map.empty
210-
-- Reuse the body generator
211-
bodyBlocks <- liftGen $ evalStateT genBodyBlocks localEnv
212-
let argNames = Map.keys localEnv
213-
args = fromList' () (map (ExpValue () nullSpan . ValVariable) argNames)
214-
declBlocks = map (BlStatement () nullSpan Nothing) argDecls
215-
pure $ PUFunction annotation nullSpan Nothing (Nothing, Nothing)
216-
procName args Nothing (declBlocks ++ bodyBlocks) Nothing
231+
genProcedure :: GenM (ProgramUnit A0)
232+
genProcedure = do
233+
-- Choose if we are generating a subroutine or function
234+
isSubroutine <- liftGen (arbitrary :: Gen Bool)
235+
name <- freshName (if isSubroutine then Sub else Fun)
236+
237+
annotation <- liftGen $ arbitrary
238+
sz <- liftGen getSize
239+
numArgs <- liftGen $ choose (0, max 1 (sz `div` 5))
240+
-- Generate the parameters in a fresh local environment (own scope),
241+
-- reusing genDecls; the resulting env gives us the argument names.
242+
env_before <- get
243+
-- Blank out local variables
244+
modify (\env -> env { localVariables = Map.empty } )
245+
argDecls <- genDecls False numArgs
246+
let argTypes = map fst argDecls
247+
248+
-- Generate parameters and declaration statements for the parameter
249+
env <- get
250+
let argNames = Map.keys (localVariables env)
251+
args = fromList' () (map (ExpValue () nullSpan . ValVariable) argNames)
252+
declBlocks = map (\(_, s) -> BlStatement () nullSpan Nothing s) argDecls
253+
254+
-- Generate the body of the procedure
255+
bodyBlocks <- genBodyBlocks
256+
257+
-- Produce the final procedure AST node, updating the type environment
258+
pu <- if isSubroutine
259+
then do
260+
modify (\env -> env { subroutines = Map.insert name argTypes (subroutines env) })
261+
pure $ PUSubroutine annotation nullSpan (Nothing, Nothing) name args (declBlocks ++ bodyBlocks) Nothing
262+
else do
263+
264+
-- Decide what the return result will be for a function
265+
returnType <- liftGen (arbitrary :: Gen (TypeSpec A0))
266+
returnValue <- genTypedExpression returnType
267+
-- Functions return by assigning to their own name
268+
let returnBlock = BlStatement () nullSpan Nothing
269+
(StExpressionAssign () nullSpan (ExpValue () nullSpan (ValVariable name)) returnValue)
270+
271+
modify (\env -> env { functions = Map.insert name (argTypes, returnType) (functions env) })
272+
273+
pure $ PUFunction annotation nullSpan (Just returnType) (Nothing, Nothing)
274+
name args Nothing (declBlocks ++ bodyBlocks ++ [returnBlock]) Nothing
275+
276+
-- Restore the caller's local variables so procedure locals don't leak
277+
modify (\env -> env { localVariables = localVariables env_before })
278+
pure pu
217279

218280
-- Synthesise an expression of the given type
219281
genTypedExpression :: TypeSpec A0 -> GenM (Expression A0)
220282
genTypedExpression typeSpec = do
221-
-- For simplicity, we just generate a variable reference of the correct type.
222-
-- In a full implementation, we would generate more complex expressions.
223-
env <- get
224-
-- See if a variable can fill the hole
225-
let candidates = [name | (name, t) <- Map.toList env, t == typeSpec]
226-
-- If not...
227-
if null candidates
228-
-- No variables of the correct type, fall back to arbitrary expression
229-
then genTypedValue typeSpec
230-
else do
231-
-- Otherwise generate extpressions from the variables
232-
name <- liftGen $ elements candidates
233-
annotation <- liftGen $ arbitrary
234-
value <- oneofCtxt [ pure (ExpValue annotation nullSpan $ ValVariable name)
235-
, genTypedValue typeSpec ] -- In a full implementation, we would generate more complex expressions
236-
pure value
283+
-- Choose a strategy: variable, value, or expression
284+
oneofCtxt [variable, genTypedValue typeSpec]
285+
286+
where
287+
-- TODO: fancier stuff here
288+
expression :: GenM (Expression A0)
289+
expression = genTypedValue typeSpec
290+
291+
variable :: GenM (Expression A0)
292+
variable = do
293+
env <- get
294+
-- See if a variable can fill the hole
295+
let candidates = [name | (name, t) <- Map.toList (localVariables env), t == typeSpec]
296+
-- If not...
297+
if null candidates
298+
-- No variables of the correct type, fall back to arbitrary expression
299+
then genTypedValue typeSpec
300+
else do
301+
-- Otherwise generate expressions from the variables
302+
name <- liftGen $ elements candidates
303+
annotation <- liftGen $ arbitrary
304+
value <- oneofCtxt [ pure (ExpValue annotation nullSpan $ ValVariable name)
305+
, genTypedValue typeSpec ] -- In a full implementation, we would generate more complex expressions
306+
pure value
237307

238308
-- Synthesise a value of the given type
239309
genTypedValue :: TypeSpec A0 -> GenM (Expression A0)
@@ -254,11 +324,11 @@ genTypedValue (TypeSpec _ _ baseType _) = case baseType of
254324
let s' = concat (map (\c -> if c == '\'' then "" else if c == '\"' then "\\\"" else [c]) s)
255325
pure $ ExpValue () nullSpan (ValString s')
256326

257-
instance ArbitraryCtxt a => ArbitraryCtxt [a] where
258-
arbitraryCtxt = do
327+
instance ArbitraryInCtxt a => ArbitraryInCtxt [a] where
328+
arbitraryInCtxt = do
259329
sz <- liftGen getSize
260330
n <- liftGen $ choose (0, sz)
261-
replicateM n arbitraryCtxt
331+
replicateM n arbitraryInCtxt
262332

263333
oneofCtxt :: [GenM a] -> GenM a
264334
oneofCtxt gens = do
@@ -268,7 +338,7 @@ oneofCtxt gens = do
268338
pickVar :: GenM (Name, TypeSpec A0)
269339
pickVar = do
270340
env <- get
271-
liftGen $ elements (Map.toList env)
341+
liftGen $ elements (Map.toList $ localVariables env)
272342

273343
--------------------------------------------------------------------------------
274344
-- Demonstration / experimentation

0 commit comments

Comments
 (0)