1 (* Goalfile Abstract Syntax Tree
2 * Copyright (C) 2019 Richard W.M. Jones
3 * Copyright (C) 2019 Red Hat Inc.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 let pure_cache = Hashtbl.create 13
26 let rec to_constant env = function
27 | Ast.EConstant (loc, c) -> c
30 let expr = Ast.getvar env loc name in
33 | ESubsts (loc, str) ->
34 CString (substitute env loc str)
37 failwithf "%a: list found where constant expression expected"
40 | ECall (loc, name, args) ->
41 let expr = Ast.getvar env loc name in
44 (* Goals don't return anything so they cannot be used in
45 * constant expressions. Use a function instead.
47 failwithf "%a: cannot use goal call ‘%s’ in shell expansion"
48 Ast.string_loc loc name
50 | EFuncDefn (loc, func) ->
51 to_constant env (call_function env loc name args func)
54 failwithf "%a: cannot use ‘%s’ in constant expression"
55 Ast.string_loc loc name
58 | ETacticCtor (loc, name, _) ->
59 failwithf "%a: cannot use tactic ‘%s’ in constant expression"
60 Ast.string_loc loc name
62 | EGoalDefn (loc, _) ->
63 failwithf "%a: cannot use goal in constant expression"
66 | EFuncDefn (loc, _) ->
67 failwithf "%a: cannot use function in constant expression"
70 | ETacticDefn (loc, _) ->
71 failwithf "%a: cannot use tactic in constant expression"
74 and substitute env loc substs =
75 let b = Buffer.create 13 in
78 | Ast.SString s -> Buffer.add_string b s
80 let expr = Ast.getvar env loc name in
81 match to_constant env expr with
82 | Ast.CString s -> Buffer.add_string b s
86 and to_shell_script env loc substs =
87 let b = Buffer.create 13 in
90 | Ast.SString s -> Buffer.add_string b s
92 let expr = Ast.getvar env loc name in
93 let s = expr_to_shell_string env expr in
98 and expr_to_shell_string env = function
99 | Ast.EConstant (loc, CString s) -> Filename.quote s
101 | EVar (loc, name) ->
102 let expr = Ast.getvar env loc name in
103 expr_to_shell_string env expr
105 | ESubsts (loc, str) ->
106 Filename.quote (substitute env loc str)
108 | EList (loc, exprs) ->
109 let strs = List.map (expr_to_shell_string env) exprs in
110 (* These are shell quoted so we can just concat them with space. *)
111 String.concat " " strs
113 | ECall (loc, name, args) ->
114 let expr = Ast.getvar env loc name in
117 (* Goals don't return anything so they cannot be used in
118 * shell script expansions. Use a function instead.
120 failwithf "%a: cannot use goal call ‘%s’ in shell expansion"
121 Ast.string_loc loc name
123 | EFuncDefn (loc, func) ->
124 expr_to_shell_string env (call_function env loc name args func)
127 failwithf "%a: cannot call ‘%s’ which is not a function"
128 Ast.string_loc loc name
131 (* Tactics expand to the first parameter. *)
132 | ETacticCtor (loc, _, []) -> Filename.quote ""
133 | ETacticCtor (loc, _, (arg :: _)) -> expr_to_shell_string env arg
135 | EGoalDefn (loc, _) ->
136 failwithf "%a: cannot use goal in shell expansion"
139 | EFuncDefn (loc, _) ->
140 failwithf "%a: cannot use function in shell expansion"
143 | ETacticDefn (loc, _) ->
144 failwithf "%a: cannot use tactic in shell expansion"
147 and run_code env loc code =
148 let code = prepare_code env loc code in
151 and run_code_to_string env loc code =
152 let code = prepare_code env loc code in
153 let chan = Unix.open_process_in code in
154 let b = Buffer.create 1024 in
157 Buffer.add_string b (input_line chan);
158 Buffer.add_char b '\n'
160 with End_of_file -> ());
161 let st = Unix.close_process_in chan in
164 | Unix.WEXITED i -> i
165 | Unix.WSIGNALED i ->
166 failwithf "%a: killed by signal %d" Ast.string_loc loc i
168 failwithf "%a: stopped by signal %d" Ast.string_loc loc i in
171 and run_code_to_string_list env loc code =
172 let code = prepare_code env loc code in
173 let chan = Unix.open_process_in code in
174 let lines = ref [] in
175 (try while true do lines := input_line chan :: !lines done
176 with End_of_file -> ());
177 let st = Unix.close_process_in chan in
180 | Unix.WEXITED i -> i
181 | Unix.WSIGNALED i ->
182 failwithf "%a: killed by signal %d" Ast.string_loc loc i
184 failwithf "%a: stopped by signal %d" Ast.string_loc loc i in
185 let lines = List.rev !lines in
188 and prepare_code env loc (code, quiet) =
189 let quiet = if Cmdline.debug_flag () then false else quiet in
190 let code = to_shell_script env loc code in
191 "source " ^ Filename.quote Cmdline.prelude_sh_file ^ "\n" ^
193 (if not (Cmdline.silent ()) && not quiet then "set -x\n" else "") ^
197 and evaluate_goal_arg env = function
198 | Ast.EVar (loc, name) ->
199 let expr = Ast.getvar env loc name in
200 evaluate_goal_arg env expr
202 | ESubsts (loc, str) ->
203 let str = substitute env loc str in
204 Ast.EConstant (loc, Ast.CString str)
206 | EList (loc, exprs) ->
207 Ast.EList (loc, List.map (evaluate_goal_arg env) exprs)
209 | ETacticCtor (loc, name, exprs) ->
210 Ast.ETacticCtor (loc, name, List.map (evaluate_goal_arg env) exprs)
212 | ECall (loc, name, args) ->
213 let expr = Ast.getvar env loc name in
216 (* Goals don't return anything so they cannot be used in
217 * goal args. Use a function instead.
219 failwithf "%a: cannot use goal call ‘%s’ in goal argument"
220 Ast.string_loc loc name
222 | EFuncDefn (loc, func) ->
223 call_function env loc name args func
226 failwithf "%a: cannot call ‘%s’ which is not a function"
227 Ast.string_loc loc name
233 | ETacticDefn _ as e -> e
235 (* Functions are only called from goal args or when substituting
236 * into a shell script or constant expression (this may change if we
237 * implement ‘:=’ assignment for variables). This evaluates a
238 * function by running the associated shell script and parsing
239 * the output as an expression, string or list of strings.
241 and call_function env loc name args (params, returning, pure, code) =
242 (* This is used to print the function in debug and error messages only. *)
244 sprintf "%s (%s) returning %s" name
245 (String.concat ", " (List.map (Ast.string_expr ()) args))
246 (match returning with RetExpr -> "expression"
247 | RetString -> "string"
248 | RetStrings -> "strings") in
249 Cmdline.debug "%a: running function %s" Ast.string_loc loc debug_func;
251 (* Evaluate function args. Must be done before updating the environment. *)
252 let args = List.map (evaluate_goal_arg env) args in
254 (* Create a new environment which maps the parameter names to
259 try List.combine params args
260 with Invalid_argument _ ->
261 failwithf "%a: calling function %s with wrong number of arguments, expecting %d args but got %d args"
262 Ast.string_loc loc debug_func
263 (List.length params) (List.length args) in
264 List.fold_left (fun env (k, v) -> Ast.Env.add k v env) env params in
266 if pure then call_function_pure env loc name returning code
267 else call_function_really env loc name returning code
269 and call_function_really env loc name returning code =
272 let r, b = run_code_to_string env loc code in
274 failwithf "function ‘%s’ failed with exit code %d" name r;
275 Parse.parse_expr (sprintf "function:%s" name) b
278 let r, b = run_code_to_string env loc code in
280 failwithf "function ‘%s’ failed with exit code %d" name r;
281 (* Remove a single trailing \n if present. *)
283 let len = String.length b in
284 if len > 0 && b.[len-1] = '\n' then String.sub b 0 (len-1) else b in
285 Ast.EConstant (loc, Ast.CString b)
288 let r, lines = run_code_to_string_list env loc code in
289 let strs = List.map (fun s -> Ast.EConstant (loc, Ast.CString s)) lines in
292 (* For pure functions, check if the function can be matched to
293 * a previously memoized result, but don't fail if this goes wrong.
295 and call_function_pure env loc name returning code =
297 try Some (to_shell_script env loc (fst code))
298 with Failure _ -> None in
300 | None -> call_function_really env loc name returning code
303 try Some (Hashtbl.find pure_cache code_key)
304 with Not_found -> None in
308 let expr = call_function_really env loc name returning code in
309 Hashtbl.add pure_cache code_key expr;