open Ast
exception Fsm_error of string
let raise_fsm_error strlist = raise (Fsm_error (String.concat "" strlist))
let concat s = String.concat "" s
type state_node = int * int list * bool ref
let node_list = ref []
let state_env = ref []
let get_state_name id =
if List.mem_assoc id !state_env then
List.assoc id !state_env
else
string_of_int id
let rec reverse_assoc lst = match lst with
(x,y)::rest -> (y,x)::(reverse_assoc rest)
| [] -> []
let rec get_state_helper node_list search_id = match node_list with
(id,targets,visited)::rest when id = search_id -> (id,targets,visited)
| _::rest -> get_state_helper rest search_id
| [] -> raise_fsm_error ["State machine error: state ";
get_state_name search_id;
" has an incoming transition but is not defined."]
let get_state search_id = get_state_helper !node_list search_id
let rec visit_graph node_list =
match node_list with
(id,targets,visited)::rest ->
if !visited then
visit_graph rest
else
let _ = visited := true in
let _ = visit_graph (List.map get_state targets) in
visit_graph rest
| [] -> ()
let check_connected start_nodes node_list =
let _ = visit_graph start_nodes in
let rec helper node_list = match node_list with
(id,_,visited)::rest ->
if !visited then
helper rest
else
raise_fsm_error ["State machine error: state graph is disconnected. State ";
get_state_name id;
" is not reachable."]
| [] -> ()
in
helper node_list
let cur_id = ref (-1);;
let make_id () = let _ = cur_id := !cur_id + 1 in !cur_id
let rec get_first lst = match lst with
(x,y)::rest -> x::(get_first rest)
| [] -> []
let rec get_state_names statelist = match statelist with
(name,_,_)::rest ->
name::(get_state_names rest)
| [] -> []
let append_string app_str str =
concat [str;app_str]
let get_id name env =
if List.mem_assoc name env then
List.assoc name env, env
else
let id = make_id() in
id, (name,id)::env
let varify str = SynVarIdentifier (SynVarName str)
let rec compile_callbacks callbacks state_name =
match callbacks with
cur::rest ->
let field, foo =
(match cur with
SynFsmOnInit s -> "onInit", s
| SynFsmOnMoveRequest s -> "onMoveRequest", s
| SynFsmOnTeammateDeath s -> "onTeammateDeath", s
| SynFsmOnBombDetonate s -> "onBombDetonate", s
| SynFsmOnDeath s -> "onDeath", s)
in
(if field = "onInit" then
[ SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName state_name,
"init",
ref VIdUnspecified),
SynVarIdentifier
(SynVarName foo))) ]
else
[ SynVarAssign
(SynVarAssignment
((SynStructOrEnumValue
(SynVarName (concat [state_name;"_callbacks"]),
field, ref VIdUnspecified)),
(SynVarIdentifier
(SynVarName foo)))) ])
@ compile_callbacks rest state_name
| [] -> []
let compile_transition_target (targ_name,targ_weight) transition_name env =
let targ_id, new_env =
if List.mem_assoc targ_name env then
List.assoc targ_name env, env
else
let id = make_id() in
id, (targ_name,id)::env
in
let tuple_name = concat [transition_name;"_target_";string_of_int (make_id())] in
[
SynVarDeclare
(SynVarDeclareNoInit
(SynEnumOrStructType
("fsmTransitionTarget", ref []),
tuple_name));
SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName tuple_name,
"id",
ref VIdUnspecified),
SynValue
(SynIntValue targ_id)));
SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName tuple_name,
"weight",
ref VIdUnspecified),
SynValue
(SynFloatValue targ_weight)));
SynVarAssign
(SynVarModify
(SynStructOrEnumValue
(SynVarName transition_name,
"targets",
ref VIdUnspecified),
SynBinopCons,
SynVarIdentifier
(SynVarName tuple_name))) ], new_env, targ_id
let rec compile_transition_targets targs transition_name env = match targs with
cur::rest ->
let cur_result, cur_env, targ_id = compile_transition_target cur transition_name env in
let rest_result, rest_env, targ_ids = compile_transition_targets rest transition_name cur_env in
cur_result @ rest_result, rest_env, targ_id::targ_ids
| [] -> [], env, []
let compile_transition (preds,targs) state_name env =
let name = concat [state_name;"_transition_";string_of_int (make_id())] in
let predvars = List.map varify preds in
let target_result, target_env, target_ids = compile_transition_targets targs name env in
[
SynVarDeclare
(SynVarDeclareNoInit
(SynEnumOrStructType ("fsmTransition",ref []),
name));
SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName name,
"predicates",
ref VIdUnspecified),
(SynListValueProducer
(SynListList predvars))))]
@ target_result
@ [ SynVarAssign
(SynVarModify
(SynStructOrEnumValue
(SynVarName state_name,
"transitionList",
ref VIdUnspecified),
SynBinopCons,
(SynVarIdentifier
(SynVarName name)))) ], target_env, target_ids
let rec compile_transitions transitions state_name env =
match transitions with
cur::rest ->
let cur_result, cur_env, targ_ids = compile_transition cur state_name env in
let rest_result, rest_env, id_list = compile_transitions rest state_name cur_env in
cur_result @ rest_result, rest_env, targ_ids @ id_list
| [] -> [], env, []
let rec compile_states_helper state_list env = match state_list with
(name, callback_list, transition_list)::rest ->
let id,new_env = get_id name env in
let transition_code, transition_env, transition_ids = compile_transitions transition_list name new_env in
let _ = node_list := (id,transition_ids,ref false)::!node_list in
[
SynVarDeclare
(SynVarDeclareNoInit
(SynEnumOrStructType ("fsmState",ref []),
name));
SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName name,"id",ref VIdUnspecified),
SynValue
(SynIntValue id)));
SynVarDeclare
(SynVarDeclareNoInit
(SynEnumOrStructType ("callbackSet",ref []),
concat [name;"_callbacks"]))]
@
compile_callbacks callback_list name
@ [
SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName name,"callbacks",ref VIdUnspecified),
SynVarIdentifier
(SynVarName (concat [name;"_callbacks"])))) ]
@ transition_code
@ compile_states_helper rest transition_env
| [] -> let _ = state_env := reverse_assoc env in []
let compile_set_statelist state_names fsm_name =
let state_name_vars = List.map varify state_names in
[ SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName "ret",
"states",
ref VIdUnspecified),
SynListValueProducer
(SynListList state_name_vars))) ]
let compile_states state_list fsm_name =
let states = compile_states_helper state_list [] in
match state_list with
(init_name,_,_)::rest ->
states
@ [SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName "ret","currentState",ref VIdUnspecified),
SynVarIdentifier
(SynVarName init_name)))]
@ (compile_set_statelist (get_state_names state_list) fsm_name)
| [] -> raise_fsm_error ["State machine error: No states specified for state machine."]
let rec compile_fsm src_prog =
match src_prog with
(SynStateMachine (name,init,state_list))::rest ->
let _ = node_list := [] in
let machine_code =
[SynVarDeclare
(SynVarDeclareNoInit
(SynEnumOrStructType ("finiteStateMachine",ref []),
"ret"));
SynVarAssign
(SynVarAssignment
(SynStructOrEnumValue
(SynVarName "ret","initialize",ref VIdUnspecified),
SynVarIdentifier
(SynVarName init)))]
@ compile_states state_list name
@ [SynReturnStatement
(SynValueReturn
(SynVarIdentifier
(SynVarName "ret")))]
in
let machine_function = (SynFunctionDefine ((SynEnumOrStructType ("finiteStateMachine",ref []), name, []), machine_code)) in
let start_node = match List.rev !node_list with
start::rest -> start
| [] -> raise_fsm_error ["State mahine error: no states specified."]
in
let _ = check_connected [start_node] !node_list in
(SynInclude (ref (IncludeFileName "lib_fsm.bmd")))::(machine_function::(compile_fsm rest))
| cur::rest -> cur::(compile_fsm rest)
| [] -> []