@@ -4,7 +4,7 @@ module Language.Fortran.Generate where
44import Language.Fortran.AST
55import Language.Fortran.AST.Literal
66import Language.Fortran.AST.Literal.Real
7- import Test.QuickCheck
7+ import Test.QuickCheck hiding ( Fun )
88
99import Language.Fortran.Util.Position
1010import 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'.
84101type 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 )
124140genDecl 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 ) ]
139155genDecls initialise n = replicateM n (genDecl initialise)
140156
141157-- | Generate a program unit with a growing set of declarations.
142158instance 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
181209genBodyBlocks :: GenM [Block A0 ]
182210genBodyBlocks = 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)
193222genProcedures :: GenM [ProgramUnit A0 ]
194223genProcedures = 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
219281genTypedExpression :: TypeSpec A0 -> GenM (Expression A0 )
220282genTypedExpression 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
239309genTypedValue :: 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
263333oneofCtxt :: [GenM a ] -> GenM a
264334oneofCtxt gens = do
@@ -268,7 +338,7 @@ oneofCtxt gens = do
268338pickVar :: GenM (Name , TypeSpec A0 )
269339pickVar = 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