+
+(* Functions are only called from goal args or when substituting
+ * into a shell script or constant expression (this may change if we
+ * implement ‘:=’ assignment for variables). This evaluates a
+ * function by running the associated shell script and parsing
+ * the output as an expression, string or list of strings.
+ *)
+and call_function env loc name args (params, returning, pure, code) =
+ (* This is used to print the function in debug and error messages only. *)
+ let debug_func =
+ sprintf "%s (%s) returning %s" name
+ (String.concat ", " (List.map (Ast.string_expr ()) args))
+ (match returning with RetExpr -> "expression"
+ | RetString -> "string"
+ | RetStrings -> "strings") in
+ Cmdline.debug "%a: running function %s" Ast.string_loc loc debug_func;
+
+ (* Evaluate function args. Must be done before updating the environment. *)
+ let args = List.map (evaluate_goal_arg env) args in
+
+ (* Create a new environment which maps the parameter names to
+ * the args.
+ *)
+ let env =
+ let params =
+ try List.combine params args
+ with Invalid_argument _ ->
+ failwithf "%a: calling function %s with wrong number of arguments, expecting %d args but got %d args"
+ Ast.string_loc loc debug_func
+ (List.length params) (List.length args) in
+ List.fold_left (fun env (k, v) -> Ast.Env.add k v env) env params in
+
+ if pure then call_function_pure env loc name returning code
+ else call_function_really env loc name returning code
+
+and call_function_really env loc name returning code =
+ match returning with
+ | RetExpr ->
+ let r, b = run_code_to_string env loc code in
+ if r <> 0 then (
+ eprintf "*** function ‘%s’ failed with exit code %d\n" name r;
+ exit 1
+ );
+ Parse.parse_expr (sprintf "function:%s" name) b
+
+ | RetString ->
+ let r, b = run_code_to_string env loc code in
+ if r <> 0 then (
+ eprintf "*** function ‘%s’ failed with exit code %d\n" name r;
+ exit 1
+ );
+ Ast.EConstant (loc, Ast.CString b)
+
+ | RetStrings ->
+ let r, lines = run_code_to_string_list env loc code in
+ let strs = List.map (fun s -> Ast.EConstant (loc, Ast.CString s)) lines in
+ EList (loc, strs)
+
+(* For pure functions, check if the function can be matched to
+ * a previously memoized result, but don't fail if this goes wrong.
+ *)
+and call_function_pure env loc name returning code =
+ let code_key =
+ try Some (to_shell_script env loc (fst code))
+ with Failure _ -> None in
+ match code_key with
+ | None -> call_function_really env loc name returning code
+ | Some code_key ->
+ let r =
+ try Some (Hashtbl.find pure_cache code_key)
+ with Not_found -> None in
+ match r with
+ | Some expr -> expr
+ | None ->
+ let expr = call_function_really env loc name returning code in
+ Hashtbl.add pure_cache code_key expr;
+ expr