(*
* Operations:
* 1. Check that all return/export operations are in legal
* places.
* 2. Add final operations to sequences that don't already
* have them.
* 3. Add return handlers to functions that have nontrivial
* return statements.
*)
include Omake_pos.Make (struct let name = "Omake_ir_semant" end)
(*
* Synthesized attributes.
* renv_has_return : is there a non-tail return?
* renv_is_final : code after this point is never executed
*)
type renv =
{ renv_has_return : bool;
renv_is_final : bool;
renv_is_value : bool
}
(*
* Inherited attributes.
* env_in_function : currently within a function body
* env_in_cond : currently within a conditional
* env_is_tail : the current expression is in final position
*)
type env =
{ env_warnings : Lm_location.t option ref;
env_in_function : Omake_ir.return_id option;
env_in_cond : bool;
env_section_tail : bool;
env_function_tail : bool
}
(*
* Return environments.
*)
let renv_empty =
{ renv_has_return = false;
renv_is_final = false;
renv_is_value = false
}
let renv_value =
{ renv_has_return = false;
renv_is_final = false;
renv_is_value = true
}
let renv_final =
{ renv_has_return = false;
renv_is_final = true;
renv_is_value = true
}
let renv_return =
{ renv_has_return = true;
renv_is_final = true;
renv_is_value = true
}
(*
* Normal environment, not in a function.
*)
let env_empty () =
{ env_warnings = ref None;
env_in_function = None;
env_in_cond = false;
env_section_tail = false;
env_function_tail = false
}
let env_object env =
{ env_warnings = env.env_warnings;
env_in_function = None;
env_in_cond = false;
env_section_tail = false;
env_function_tail = false
}
let env_object_tail env =
{ env_warnings = env.env_warnings;
env_in_function = None;
env_in_cond = false;
env_section_tail = true;
env_function_tail = true
}
(*
* Fresh environment for a function body.
*)
let new_return_id loc v =
let _, v = Omake_ir_util.var_of_var_info v in
loc, Lm_symbol.to_string v
let env_fun env id =
{ env_warnings = env.env_warnings;
env_in_function = Some id;
env_in_cond = false;
env_section_tail = true;
env_function_tail = true
}
let env_anon_fun env =
{ env with env_in_cond = true;
env_section_tail = true
}
let update_return renv has_return =
{ renv with renv_has_return = renv.renv_has_return || has_return }
(*
* Error checkers.
*)
let check_return_placement env loc =
match env.env_in_function with
None ->
let pos = string_pos "check_in_function" (loc_exp_pos loc) in
let print_error buf =
Format.fprintf buf "@[<v 0>Misplaced return statement.";
Format.fprintf buf "@ The return is not within a function.@]"
in
raise (Omake_value_type.OmakeException (pos, LazyError print_error))
| Some id ->
if not (env.env_function_tail || env.env_section_tail && env.env_in_cond) then begin
Lm_printf.eprintf "@[<v 3>*** omake warning: %a@ statements after this return are not reached@]@." Lm_location.pp_print_location loc;
env.env_warnings := Some loc
end;
id
(* let check_section_tail env loc = *)
(* if not env.env_section_tail then *)
(* let pos = string_pos "check_section_tail" (loc_exp_pos loc) in *)
(* raise (Omake_value_type.OmakeException (pos, StringError "This should be the last expression in the section.")) *)
let check_object_tail env loc =
if env.env_in_function <> None || not env.env_section_tail then
let pos = string_pos "check_object_tail" (loc_exp_pos loc) in
raise (Omake_value_type.OmakeException (pos, StringError "This should be the last expression in the object."))
(*
* Convert a string expression.
*)
let rec build_string env s =
let env = { env with env_function_tail = false } in
match s with
Omake_ir.NoneString _
| IntString _
| FloatString _
| WhiteString _
| ConstString _
| ThisString _
| KeyApplyString _
| VarString _ ->
false, s
| FunString (loc, opt_params, params, e, export) ->
(* Returns propagate -through- anonymous functions *)
let renv, e = build_sequence_exp (env_anon_fun env) e in
let has_return, opt_params = build_keyword_param_list env opt_params in
renv.renv_has_return || has_return, FunString (loc, opt_params, params, e, export)
| ApplyString (loc, v, args, kargs) ->
let has_return1, args = build_string_list env args in
let has_return2, kargs = build_keyword_string_list env kargs in
has_return1 || has_return2, ApplyString (loc, v, args, kargs)
| SuperApplyString (loc, v1, v2, args, kargs) ->
let has_return1, args = build_string_list env args in
let has_return2, kargs = build_keyword_string_list env kargs in
has_return1 || has_return2, SuperApplyString (loc, v1, v2, args, kargs)
| MethodApplyString (loc, v, vl, args, kargs) ->
let has_return1, args = build_string_list env args in
let has_return2, kargs = build_keyword_string_list env kargs in
has_return1 || has_return2, MethodApplyString (loc, v, vl, args, kargs)
| SequenceString (loc, sl) ->
let has_return, sl = build_string_list env sl in
has_return, SequenceString (loc, sl)
| ArrayString (loc, sl) ->
let has_return, sl = build_string_list env sl in
has_return, ArrayString (loc, sl)
| ArrayOfString (loc, s) ->
let has_return, s = build_string env s in
has_return, ArrayOfString (loc, s)
| QuoteString (loc, sl) ->
let has_return, sl = build_string_list env sl in
has_return, QuoteString (loc, sl)
| QuoteStringString (loc, c, sl) ->
let has_return, sl = build_string_list env sl in
has_return, QuoteStringString (loc, c, sl)
| ObjectString (loc, el, export) ->
let el = build_object_exp env el in
(* XXX: we should handle the case when an object contains a return *)
false, ObjectString (loc, el, export)
| BodyString (loc, el, export) ->
let renv, el = build_sequence_exp env el in
renv.renv_has_return, BodyString (loc, el, export)
| ExpString (loc, el, export) ->
let renv, el = build_sequence_exp env el in
renv.renv_has_return, ExpString (loc, el, export)
| CasesString (loc, cases) ->
let env = { env with env_in_cond = true } in
let has_return, cases =
List.fold_left (fun (has_return, cases) (v, s, el, export) ->
let has_return2, s = build_string env s in
let renv, e = build_sequence_exp env el in
let has_return = has_return || has_return2 || renv.renv_has_return in
has_return, (v, s, e, export) :: cases) (false, []) cases
in
has_return, CasesString (loc, List.rev cases)
| LazyString (loc, s) ->
let has_return, s = build_string env s in
has_return, LazyString (loc, s)
| LetVarString (loc, v, s1, s2) ->
let has_return1, s1 = build_string env s1 in
let has_return2, s2 = build_string env s2 in
has_return1 || has_return2, LetVarString (loc, v, s1, s2)
and build_string_list env sl =
let has_return, sl =
List.fold_left (fun (has_return, sl) s ->
let has_return2, s = build_string env s in
has_return || has_return2, s :: sl) (false, []) sl
in
has_return, List.rev sl
and build_keyword_string_list env kargs =
let has_return, kargs =
List.fold_left (fun (has_return, sl) (v, s) ->
let has_return2, s = build_string env s in
has_return || has_return2, (v, s) :: sl) (false, []) kargs
in
has_return, List.rev kargs
and build_keyword_param_list env kargs =
let has_return, kargs =
List.fold_left (fun (has_return, sl) (v, v_info, s_opt) ->
match s_opt with
Some s ->
let has_return2, s = build_string env s in
has_return || has_return2, (v, v_info, Some s) :: sl
| None ->
has_return, (v, v_info, None) :: sl) (false, []) kargs
in
has_return, List.rev kargs
(*
* Convert the current expression.
*)
and build_exp env e =
match e with
Omake_ir.LetFunExp (loc, v, vl, curry, opt_params, vars, el, export) ->
let id = new_return_id loc v in
let renv, el = build_sequence_exp (env_fun env id) el in
let el =
if renv.renv_has_return then
Omake_ir.[ReturnBodyExp (loc, el, id)]
else
el
in
let has_return, opt_params = build_keyword_param_list env opt_params in
let e = Omake_ir.LetFunExp (loc, v, vl, curry, opt_params, vars, el, export) in
update_return renv_empty has_return, e
| LetObjectExp (loc, v, vl, s, el, export) ->
let el = build_object_exp env el in
let has_return, s = build_string env s in
let e = Omake_ir.LetObjectExp (loc, v, vl, s, el, export) in
update_return renv_empty has_return, e
| StaticExp (loc, node, v, el) ->
let el = build_object_exp env el in
let e = Omake_ir.StaticExp (loc, node, v, el) in
renv_empty, e
| IfExp (loc, cases) ->
let renv, cases = build_cases_exp env cases in
let e = Omake_ir.IfExp (loc, cases) in
renv, e
| SequenceExp (loc, el) ->
let renv, el = build_sequence_exp env el in
let e = Omake_ir.SequenceExp (loc, el) in
renv, e
| SectionExp (loc, s, el, export) ->
let has_return, s = build_string env s in
let renv, el = build_sequence_exp env el in
let e = Omake_ir.SectionExp (loc, s, el, export) in
update_return renv has_return, e
| ReturnBodyExp (loc, el, id) ->
let renv, el = build_sequence_exp env el in
let el = Omake_ir.ReturnBodyExp (loc, el, id) in
renv, el
| LetVarExp (loc, v, vl, kind, s) ->
let has_return, s = build_string env s in
let e = Omake_ir.LetVarExp (loc, v, vl, kind, s) in
update_return renv_empty has_return, e
| IncludeExp (loc, s, sl) ->
let has_return1, s = build_string env s in
let has_return2, sl = build_string_list env sl in
let e = Omake_ir.IncludeExp (loc, s, sl) in
update_return renv_empty (has_return1 || has_return2), e
| ApplyExp (loc, v, args, kargs) ->
let has_return1, args = build_string_list env args in
let has_return2, kargs = build_keyword_string_list env kargs in
let e = Omake_ir.ApplyExp (loc, v, args, kargs) in
update_return renv_empty (has_return1 || has_return2), e
| SuperApplyExp (loc, v1, v2, args, kargs) ->
let has_return1, args = build_string_list env args in
let has_return2, kargs = build_keyword_string_list env kargs in
let e = Omake_ir.SuperApplyExp (loc, v1, v2, args, kargs) in
update_return renv_empty (has_return1 || has_return2), e
| MethodApplyExp (loc, v, vl, args, kargs) ->
let has_return1, args = build_string_list env args in
let has_return2, kargs = build_keyword_string_list env kargs in
let e = Omake_ir.MethodApplyExp (loc, v, vl, args, kargs) in
update_return renv_empty (has_return1 || has_return2), e
| LetKeyExp (loc, v, kind, s) ->
let has_return, s = build_string env s in
let e = Omake_ir.LetKeyExp (loc, v, kind, s) in
update_return renv_empty has_return, e
| LetThisExp (loc, s) ->
let has_return, s = build_string env s in
let e = Omake_ir.LetThisExp (loc, s) in
update_return renv_empty has_return, e
| ShellExp (loc, s) ->
let has_return, s = build_string env s in
let e = Omake_ir.ShellExp (loc, s) in
update_return renv_value has_return, e
| KeyExp _
| OpenExp _ ->
renv_empty, e
| StringExp (loc, s) ->
let has_return, s = build_string env s in
let e = Omake_ir.StringExp (loc, s) in
update_return renv_value has_return, e
| ReturnExp (loc, s, _) ->
let id = check_return_placement env loc in
let has_return, s = build_string env s in
if env.env_function_tail then
update_return renv_final has_return, Omake_ir.StringExp (loc, s)
else
renv_return, Omake_ir.ReturnExp (loc, s, id)
| ReturnObjectExp (loc, _)
| ReturnSaveExp loc ->
check_object_tail env loc;
renv_final, e
(*
* An object expression is an expression sequence,
* but it is not in a function.
*)
and build_object_exp_aux env el =
match el with
[e] ->
let _, e = build_exp (env_object_tail env) e in
[e]
| e :: el ->
let _, e = build_exp env e in
let el = build_object_exp_aux env el in
e :: el
| [] ->
[]
and build_object_exp env el =
build_object_exp_aux (env_object env) el
(*
* A new sequence expression.
* It should be terminated with a final statement.
*)
and build_sequence_exp env el =
let env_non_tail =
{ env with env_section_tail = false;
env_function_tail = false
}
in
let rec build_sequence_core has_return rel el =
match el with
[e] ->
let env_tail = { env with env_section_tail = true } in
let renv, e = build_exp env_tail e in
let rel = e :: rel in
update_return renv has_return, rel
| e :: el ->
let renv, e = build_exp env_non_tail e in
let has_return = has_return || renv.renv_has_return in
let rel = e :: rel in
build_sequence_core has_return rel el
| [] ->
renv_empty, []
in
let renv, rel = build_sequence_core false [] el in
renv, List.rev rel
(*
* Cases are slightly different from sequences because
* returns are always allowed. Note that the completeness is
* not checked, so even if all cases end in a return,
* evaluation may continue from here.
*)
and build_cases_exp env cases =
let env =
{ env with env_in_cond = true;
env_section_tail = true
}
in
let has_return, cases =
List.fold_left (fun (has_return, cases) (s, el, export) ->
let renv, el = build_sequence_exp env el in
let has_return = has_return || renv.renv_has_return in
has_return, (s, el, export) :: cases) (false, []) cases
in
let cases = List.rev cases in
let renv =
{ renv_is_final = false;
renv_is_value = true;
renv_has_return = has_return
}
in
renv, cases
(************************************************************************
* Main function
*)
let build_prog venv e =
let env = env_empty () in
let _, e = build_exp env e in
let count = !(env.env_warnings) in
let () =
match count with
|Some loc when Omake_options.opt_warn_error (Omake_env.venv_options venv) ->
raise (Omake_value_type.OmakeException (loc_exp_pos loc, StringError "warnings treated as errors"))
| _ -> () in
e