and constant =
| CString of string
and goal = param_decl list * pattern list * expr list * code option
-and func = param_decl list * returning * code
+and func = param_decl list * returning * bool * code
and tactic = param_decl list * code
and param_decl = id
and id = string
| Some (code, false) -> " = { ... }"
| Some (code, true) -> " = @{ ... }")
-and string_func () (name, (param_decls, returning, (code, quiet))) =
- sprintf "function%s returning %s (%s) = %s{ ... }"
+and string_func () (name, (param_decls, returning, pure, (code, quiet))) =
+ sprintf "%sfunction%s returning %s (%s) = %s{ ... }"
+ (if pure then "pure " else "")
(match name with None -> "" | Some name -> " " ^ name)
(match returning with RetExpr -> "expression"
| RetString -> "string"
and constant =
| CString of string
and goal = param_decl list * pattern list * expr list * code option
-and func = param_decl list * returning * code
+and func = param_decl list * returning * bool * code
and tactic = param_decl list * code
and param_decl = id (** goal/func/tactic parameter. *)
and id = string
open Utils
+let pure_cache = Hashtbl.create 13
+
let rec to_constant env = function
| Ast.EConstant (loc, c) -> c
* 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, code) =
+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
(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 =
let r, b = run_code env loc code in
if r <> 0 then (
eprintf "*** function ā%sā failed with exit code %d\n" name r;
let strs = List.rev strs in
let strs = List.map (fun s -> Ast.EConstant (loc, Ast.CString s)) strs 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
{ TACTIC_KEYWORD }
| "function"
{ FUNCTION }
+ | "pure" { PURE }
| "let" { LET }
| "include"
{ INCLUDE }
%token LEFT_PAREN
%token LET
%token OPTINCLUDE
+%token PURE
%token RETURNING
%token RIGHT_ARRAY
%token RIGHT_PAREN
let name, params = $1 in
name, Ast.EGoalDefn ($loc, (params, [], [], Some $2))
}
- | FUNCTION ID params_decl return_decl EQUALS CODE
+ | option(PURE) FUNCTION ID params_decl return_decl EQUALS CODE
{
- $2, Ast.EFuncDefn ($loc, ($3, $4, $6))
+ $3, Ast.EFuncDefn ($loc, ($4, $5, $1 <> None, $7))
}
| TACTIC_KEYWORD TACTIC params_decl EQUALS CODE
{
# Text functions.
# Sort + uniq a list.
-function sort (xs) returning strings = @{
+pure function sort (xs) returning strings = @{
for f in %xs; do echo "$f"; done | sort -u
}
# File functions.
# Expand a wildcard into a list of filenames.
+#
+# This function is probably not "pure" since it depends on the
+# current working directory and also files may be created in
+# the directory while goals is running which would affect the
+# result.
function wildcard (wc) returning strings = @{
shopt -s nullglob
# Note that the substitution is quoted by goals, so to expand