33# of `TrixiBase`. However, users will want to evaluate in the global scope of `Main` or something
44# similar to manage dependencies on their own.
55"""
6- trixi_include([mapexpr::Function=identity,] [mod::Module=Main,] elixir::AbstractString; kwargs...)
6+ trixi_include([mapexpr::Function=identity,] [mod::Module=Main,] elixir::AbstractString;
7+ enable_assignment_validation::Bool = true,
8+ replace_assignments_recursive::Bool = false, kwargs...)
79
810`include` the file `elixir` and evaluate its content in the global scope of module `mod`.
911You can override specific assignments in `elixir` by supplying keyword arguments.
@@ -20,6 +22,16 @@ The optional first argument `mapexpr` can be used to transform the included code
2022it is evaluated: for each parsed expression `expr` in `elixir`, the `include` function
2123actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to `identity`.
2224
25+ With `replace_assignments_recursive=true`, the keyword arguments are also passed
26+ to nested calls of `trixi_include`. This allows to override assignments in nested files as well.
27+
28+ The keyword argument `enable_assignment_validation`, which is enabled by default,
29+ can be used to enable or disable validation that all passed keyword arguments exist
30+ as assignments in `elixir`. If `enable_assignment_validation` is `true` and
31+ an assignment for a passed keyword argument is not found in `elixir`, an error is thrown.
32+ If `replace_assignments_recursive` is `true` and `elixir` contains calls to `trixi_include`
33+ itself, a warning is issued instead of an error.
34+
2335# Examples
2436
2537```@example
@@ -34,23 +46,40 @@ julia> redirect_stdout(devnull) do
34460.1
3547```
3648"""
37- function trixi_include (mapexpr:: Function , mod:: Module , elixir:: AbstractString ; kwargs... )
49+ function trixi_include (mapexpr:: Function , mod:: Module , elixir:: AbstractString ;
50+ enable_assignment_validation:: Bool = true ,
51+ replace_assignments_recursive:: Bool = false , kwargs... )
3852 # Check that all kwargs exist as assignments
3953 code = read (elixir, String)
4054 expr = Meta. parse (" begin \n $code \n end" )
4155 expr = insert_maxiters (expr)
4256
43- for (key, val) in kwargs
44- # This will throw an error when `key` is not found
45- find_assignment (expr, key)
57+ # Validate that all kwargs exist as assignments (with warning for recursive cases).
58+ # Skip for nested calls because all kwargs are passed to all nested calls,
59+ # some of which may not use all kwargs.
60+ if enable_assignment_validation
61+ validate_assignments (expr, kwargs, elixir, replace_assignments_recursive)
4662 end
4763
4864 # Print information on potential wait time only in non-parallel case
4965 if ! mpi_isparallel ()
5066 @info " You just called `trixi_include`. Julia may now compile the code, please be patient."
5167 end
52- Base. include (ex -> mapexpr (replace_assignments (insert_maxiters (ex); kwargs... )),
53- mod, elixir)
68+
69+ if replace_assignments_recursive
70+ # Add kwarg `enable_assignment_validation` to disable validation in nested
71+ # `trixi_include` calls.
72+ Base. include (ex -> mapexpr (replace_assignments (insert_maxiters (ex),
73+ replace_assignments_recursive;
74+ enable_assignment_validation = false ,
75+ replace_assignments_recursive = true ,
76+ kwargs... )),
77+ mod, elixir)
78+ else
79+ Base. include (ex -> mapexpr (replace_assignments (insert_maxiters (ex);
80+ kwargs... )),
81+ mod, elixir)
82+ end
5483end
5584
5685function trixi_include (mod:: Module , elixir:: AbstractString ; kwargs... )
@@ -159,24 +188,98 @@ walkexpr(f, expr::Expr) = f(Expr(expr.head, (walkexpr(f, arg) for arg in expr.ar
159188walkexpr (f, x) = f (x)
160189
161190# Replace assignments to `key` in `expr` by `key = val` for all `(key,val)` in `kwargs`.
162- function replace_assignments (expr; kwargs... )
163- # replace explicit and keyword assignments
191+ function replace_assignments (expr, recursive = false ; kwargs... )
164192 expr = walkexpr (expr) do x
165193 if x isa Expr
194+ # Replace explicit and keyword assignments
166195 for (key, val) in kwargs
167196 if (x. head === Symbol (" =" ) || x. head === :kw ) &&
168197 x. args[1 ] === Symbol (key)
169198 x. args[2 ] = :($ val)
170199 # dump(x)
171200 end
172201 end
202+
203+ # If `recursive` is true:
204+ # Handle `trixi_include` calls - add kwargs to them as well.
205+ is_trixi_include = (x. head === :call && length (x. args) >= 2 &&
206+ (x. args[1 ] === :trixi_include ||
207+ x. args[1 ] === :trixi_include_changeprecision ))
208+ if ! isempty (kwargs) && is_trixi_include && recursive
209+
210+ # Check for existing kwargs (both direct :kw and bare symbols in :parameters)
211+ existing_kwargs = Set {Symbol} ()
212+ for arg in x. args[2 : end ] # Skip function name
213+ if arg isa Expr && arg. head === :kw
214+ # Direct keyword argument like `x=5` in `f(x=5)`
215+ push! (existing_kwargs, arg. args[1 ])
216+ elseif arg isa Expr && arg. head === :parameters
217+ # Keyword arguments grouped in `parameters`
218+ # like `f(; x=5)` or `f(; x)`.
219+ for nested_arg in arg. args
220+ if nested_arg isa Symbol
221+ # Bare symbol like `x` in `f(; x)`
222+ push! (existing_kwargs, nested_arg)
223+ elseif nested_arg isa Expr && nested_arg. head === :kw
224+ # Keyword argument like `x=5` in `f(; x=5)`
225+ push! (existing_kwargs, nested_arg. args[1 ])
226+ end
227+ end
228+ end
229+ end
230+
231+ # Add kwargs that don't already exist.
232+ # Note that existing keywords as assignment (`x=5`) don't need to be added
233+ # again because they are replaced in the loop
234+ # "Replace explicit and keyword assignments" above.
235+ # Bare symbol like `x` in `f(; x)` must have been defined in the file
236+ # before they are passed to `trixi_include`, so there must be an assignment
237+ # `x = ...` in the file, which will also be replaced in the loop above.
238+ for (key, val) in kwargs
239+ if ! (Symbol (key) in existing_kwargs)
240+ push! (x. args, Expr (:kw , Symbol (key), val))
241+ end
242+ end
243+ end
173244 end
174245 return x
175246 end
176247
177248 return expr
178249end
179250
251+ # Validate that keyword arguments passed to `trixi_include` exist as assignments
252+ # in the expression. Throw an error if they are not found or a warning for recursive calls.
253+ function validate_assignments (expr, assignments, filename, replace_assignments_recursive)
254+ isempty (assignments) && return
255+
256+ found_assignments = Set {Symbol} ()
257+ has_nested_calls = false
258+
259+ walkexpr (expr) do x
260+ if x isa Expr
261+ if (x. head === Symbol (" =" ) || x. head === :kw ) && x. args[1 ] isa Symbol
262+ push! (found_assignments, x. args[1 ])
263+ elseif (x. head === :call && length (x. args) >= 2 &&
264+ (x. args[1 ] === :trixi_include ||
265+ x. args[1 ] === :trixi_include_changeprecision ))
266+ has_nested_calls = true
267+ end
268+ end
269+ return x
270+ end
271+
272+ missing_assignments = setdiff (Symbol .(keys (assignments)), found_assignments)
273+ if ! isempty (missing_assignments)
274+ if replace_assignments_recursive && has_nested_calls
275+ @warn " assignments $missing_assignments not found in $filename , " *
276+ " but nested trixi_include calls detected. They may be used in nested files."
277+ else
278+ throw (ArgumentError (" assignments $missing_assignments not found in $filename " ))
279+ end
280+ end
281+ end
282+
180283# Find a (keyword or common) assignment to `destination` in `expr`
181284# and return the assigned value.
182285function find_assignment (expr, destination)
0 commit comments