package fsml

  1. Overview
  2. Docs

Source file fsm.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
(**********************************************************************)
(*                                                                    *)
(*              This file is part of the FSML library                 *)
(*                     github.com/jserot/fsml                         *)
(*                                                                    *)
(*  Copyright (c) 2020-present, Jocelyn SEROT.  All rights reserved.  *)
(*                                                                    *)
(*  This source code is licensed under the license found in the       *)
(*  LICENSE file in the root directory of this source tree.           *)
(*                                                                    *)
(**********************************************************************)

type t = {
  id: string;
  states: (State.t * Valuation.t) list;
  inps: (string * Types.t) list;
  outps: (string * Types.t) list;
  vars: (string * Types.t) list;
  trans: Transition.t list;
  itrans: State.t * Action.t list;
} [@@deriving show {with_path=false}, yojson]

(* Transformation functions *)
       
exception Unknown_output of string
                          
let mealy_outp m o =
  let remove_outp_assignation o ov =
    List.filter (fun (o',_) -> o <> o') ov in
  let add_incoming_action s act ((src,guard,acts,dst) as t) =
    if dst = s then (src, guard, act::acts, dst)
    else t in
  let add_incoming_action' s act ((dst,acts) as t) =
    if dst = s then
      (dst, act::acts)
    else
      t in
  let move_output_from_state o m (s,ovs) =
    match List.assoc_opt o ovs with
    | Some v ->
       let act = Action.Assign (o,v) in
      { m with states = Misc.replace_assoc s (remove_outp_assignation o ovs) m.states;
               trans = List.map (add_incoming_action s act) m.trans;
               itrans = add_incoming_action' s act m.itrans }
    | None ->
       m in
  if List.mem_assoc o m.outps then
    List.fold_left (move_output_from_state o) m m.states
  else
    raise (Unknown_output o)
  
let mealy_outps ?(outps=[]) m =
  let os = match outps with
    | [] -> List.map fst m.outps
    | _ -> outps in
  List.fold_left mealy_outp m os

let moore_outp m o =
  let move_output_to_state o m (s,ovs) =
    (* If all transitions ending in [s] carry the same, constant, assignation of [o], add it to [ovs] *)
    let get_assignation acts =
      match
        List.find_opt
          (function Action.Assign (o',e) -> o'=o && Expr.is_const e)
          acts with
      | None -> None
      | Some (Action.Assign (_,e)) -> Some e in
    let remove_action acts = List.filter (function Action.Assign (o',_) -> o'<>o) acts in
    let remove_incoming_action ((src,guards,acts,dst) as t) =
      if dst = s then (src, guards, remove_action acts, dst)
      else t in
    let remove_incoming_action' ((dst,acts) as t) =
      if dst = s then (dst, remove_action acts)
      else t in
    let ts = m.trans
             |> List.filter (fun (_,_,_,dst) -> dst=s)
             |> List.map (fun (_,_,acts,_) -> get_assignation acts) in
    match List.find_opt (function Some _ -> true | _ -> false) ts with
    | Some (Some e' as e) -> (* TO FIX : _ = t *)
       if List.for_all (function e'' -> e=e'') ts then
         (* Apply transformation ! I.e. remove assignation from all selected transitions and add it to state [s]. *)
         { m with trans = List.map remove_incoming_action m.trans;
                  itrans = remove_incoming_action' m.itrans;
                  states = Misc.replace_assoc s (Valuation.add o e' ovs) m.states }
       else
         m (* The transformation is not applicable *)
    | _ -> (* The transformation is not applicable *)
       m   
  in
  if List.mem_assoc o m.outps then
    List.fold_left (move_output_to_state o) m m.states
  else
    raise (Unknown_output o)
  
let moore_outps ?(outps=[]) m =
  let os = match outps with
    | [] -> List.map fst m.outps
    | _ -> outps in
  List.fold_left moore_outp m os

exception Unknown_var of string
exception Illegal_var_type of string * Types.t

let clean m =
  let reachable_states =
    List.fold_left
      (fun acc (_,_,_,dst) -> if List.mem dst acc then acc else dst::acc)
      []
      m.trans
    @ [fst m.itrans] in
  { m with states = List.map (fun s -> s, List.assoc s m.states) reachable_states;
           trans = List.filter (fun (src,_,_,_) -> List.mem src reachable_states) m.trans
  }

let defact ~cleaned m (var,range,ival) =
  let dom_v = Misc.list_make ~f:(fun v -> Expr.Int v) ~lo:range.Types.lo ~hi:range.Types.hi in 
  let remove_guard guards = List.filter (Fun.negate @@ Expr.is_var_test var) guards in
  let remove_act acts = List.filter (function Action.Assign(v,_) when v=var -> false | _ -> true) acts in
  let filter_domain (guards,acts) (u,u') =
    (* Tells whether a pair of valuations [(u,u')] for variable [var] is compatible with the
     *    specified transition guards and actions *)
    let test_guard u expr =
      if Expr.is_var_test var expr
      then Expr.bool_val (Expr.eval (Builtins.eval_env @ [var, u]) expr)
      else true in
    let test_guards u = List.for_all (test_guard u) guards in
    let test_act u u' act = match act with
      | Action.Assign (v,exp) when v=var ->
         Expr.eval (Builtins.eval_env @ [var, u]) exp = u' 
      | _ -> test_guards u' in
    let test_acts u u' =
      match List.find_opt (function Action.Assign (v,_) when v=var -> true | _ -> false) acts with
      | Some a -> (* If the list of actions contains an assignment to [var] then it is used to restrict the domain ... *)
         test_act u u' a
      | None -> (* ... else, the domain is restricted by the list of guards *)
         List.for_all (test_guard u') guards in
    test_guards u && test_acts u u' in
  let sub_state s u = s ^ Expr.string_of_value u in
  let add_states acc (s,vv) = acc @ List.map (function u -> sub_state s u, vv) dom_v in
  let add_transitions acc (q,guards,acts,q') =
      let d2v = List.filter (filter_domain (guards,acts)) (Misc.cart_prod dom_v dom_v) in
      let guards' = remove_guard guards in
      let acts' = remove_act acts in
      acc @ List.map (fun (u,u') -> sub_state q u, guards', acts', sub_state q' u') d2v in
  let add_itransition (q,acts) = sub_state q ival, remove_act acts in
  let m' =
    { m with states = List.fold_left add_states [] m.states; 
             (* Each state [s] gives a set of states [{q^u | u in domain(v)}] *)
             trans = List.fold_left add_transitions [] m.trans;
             itrans = add_itransition m.itrans;
             vars = List.remove_assoc var m.vars
    } in
  if cleaned then clean m' else m'

let defactorize_var ?(cleaned=true) m (v,ty,iv) =
  match Types.real_type ty with
  | Types.TyInt (_, _, Types.Const r) -> defact ~cleaned m (v,r,iv) 
  | _ -> raise (Illegal_var_type (v, ty))

let defactorize ~vars ?(cleaned=true) m =
  let lookup v = try List.assoc v m.vars with Not_found -> raise (Unknown_var v) in
  let vs = List.map (function (v,iv) -> v, Types.real_type (lookup v), iv) vars in
  List.fold_left (defactorize_var ~cleaned) m vs

(* Serializing/deserializing fns *)
       
let to_string m =
  m |> to_yojson |> Yojson.Safe.to_string 

let from_string s = 
  match Yojson.Safe.from_string s |> of_yojson with
  | Ok v -> v
  | Error _ -> Yojson.json_error "Fsm.from_string: invalid JSON string"

let to_file ~fname m = 
  m |> to_yojson |> Yojson.Safe.to_file fname;
  Printf.printf "Wrote file %s\n" fname
  
let from_file ~fname = 
  match fname |> Yojson.Safe.from_file |> of_yojson with
  | Ok v -> v
  | Error _ -> Yojson.json_error "Fsm.from_string: invalid JSON file"