diff options
Diffstat (limited to 'lib/checks')
-rw-r--r-- | lib/checks/check.ml | 356 | ||||
-rw-r--r-- | lib/checks/check.mli | 11 | ||||
-rw-r--r-- | lib/checks/compose.ml | 127 | ||||
-rw-r--r-- | lib/checks/dead_end.ml | 11 | ||||
-rw-r--r-- | lib/checks/dead_end.mli | 7 | ||||
-rw-r--r-- | lib/checks/default.ml | 131 | ||||
-rw-r--r-- | lib/checks/dune | 5 | ||||
-rw-r--r-- | lib/checks/dup_test.ml | 68 | ||||
-rw-r--r-- | lib/checks/dup_test.mli | 2 | ||||
-rw-r--r-- | lib/checks/dynamics.ml | 269 | ||||
-rw-r--r-- | lib/checks/dynamics.mli | 5 | ||||
-rw-r--r-- | lib/checks/get_type.ml | 291 | ||||
-rw-r--r-- | lib/checks/get_type.mli | 25 | ||||
-rw-r--r-- | lib/checks/locations.ml | 61 | ||||
-rw-r--r-- | lib/checks/nested_strings.ml | 77 | ||||
-rw-r--r-- | lib/checks/nested_strings.mli | 2 | ||||
-rw-r--r-- | lib/checks/type_of.ml | 129 | ||||
-rw-r--r-- | lib/checks/type_of.mli | 10 | ||||
-rw-r--r-- | lib/checks/write_only.ml | 43 |
19 files changed, 1046 insertions, 584 deletions
diff --git a/lib/checks/check.ml b/lib/checks/check.ml index 6169bb1..597bc0a 100644 --- a/lib/checks/check.ml +++ b/lib/checks/check.ml @@ -1,5 +1,4 @@ module S = Qsp_syntax.S -module C = Qsp_syntax.Catalog (** The the Id module, wrap a value in an existencial type with a witness associate with. *) @@ -11,10 +10,13 @@ let get : type a. a Type.Id.t -> result -> a option = | Some Type.Equal -> Some value | None -> None -type t = Qsp_syntax.Catalog.ex +let set : type a. a Type.Id.t -> result -> a -> result option = + fun typeid (R { witness; _ }) value -> + match Type.Id.provably_equal typeid witness with + | Some Type.Equal -> Some (R { witness; value }) + | None -> None -let get_module : t -> (module S.Analyzer) = - fun (E { module_; _ }) -> (module_ :> (module S.Analyzer)) +type t = Qsp_syntax.Identifier.t module type App = sig val t : t array @@ -42,23 +44,85 @@ module Make (A : App) = struct let description = "Internal module" let is_global = false let active = ref false + let depends = [] (* This modules depends of nothing *) + + type ex = Qsp_syntax.Identifier.t type context = result Array.t (** We associate each context from the differents test in an array. The context for this module is a sort of context of contexts *) + (* Initialize the modules to check here, at the module level *) + let checks = + (* Collect all the dependencies and build the execution order *) + let graph : ex list = + let rec build_deps l acc = + List.fold_left + (l : ex list) + ~init:acc + ~f:(fun + acc (Qsp_syntax.Identifier.E { module_ = (module S); _ } as ex) -> + let acc' = ex :: acc in + + build_deps S.depends acc') + in + build_deps (Array.to_list A.t) [] + in + + (* Convert the dependenciees using the module identifier only, the + Tsort.sort function use structural equality comparaison function which + does not wok with the module embeded in first class module. *) + let graph_name = + List.map graph + ~f:(fun (Qsp_syntax.Identifier.E { module_ = (module S); _ }) -> + let deps' = + List.map + ~f:(fun (Qsp_syntax.Identifier.E { module_ = (module S); _ }) -> + S.identifier) + S.depends + in + (S.identifier, deps')) + in + match Tsort.sort graph_name with + | Tsort.Sorted sorted_graph -> + (* From the identifier, extract the associated check *) + let _ = + List.map sorted_graph ~f:(fun name -> + List.find_map graph + ~f:(fun + (Qsp_syntax.Identifier.E { module_ = (module S); _ } as + check) + -> + match String.equal name S.identifier with + | false -> None + | true -> Some check) + |> Option.get) + (* It’s ok to use unchecked option.get here, because the list was + created from the same source just before *) + in + + Array.of_list graph + | Tsort.ErrorCycle _ -> + (* This is very unlikely to happen, as it would reflect an error in + the compilation units order *) + raise Not_found + (** Initialize each test, and keep the result in the context. *) let initialize : unit -> context = fun () -> - Array.map A.t ~f:(fun (C.E { module_ = (module S); context; _ }) -> + Array.map checks + ~f:(fun (Qsp_syntax.Identifier.E { module_ = (module S); context; _ }) -> let value = S.initialize () in R { value; witness = context }) let finalize : result Array.t -> (string * Qsp_syntax.Report.t) list = fun context_array -> let _, report = - Array.fold_left A.t ~init:(0, []) - ~f:(fun (i, acc) (C.E { module_ = (module S); context; _ }) -> + Array.fold_left checks ~init:(0, []) + ~f:(fun + (i, acc) + (Qsp_syntax.Identifier.E { module_ = (module S); context; _ }) + -> let result = Array.get context_array i in let local_context = Option.get (get context result) in let reports = S.finalize local_context in @@ -67,111 +131,195 @@ module Make (A : App) = struct report (* Global variable for the whole module *) - let len = Array.length A.t + let len = Array.length checks module Expression : S.Expression with type t' = result array = struct type t = result array type t' = result array - let literal : S.pos -> t Qsp_syntax.T.literal list -> t = - fun pos values -> - Array.mapi A.t - ~f:(fun i (C.E { module_ = (module S); expr_witness; _ }) -> - (* Map every values to the Checker *) - let values' = - List.map values - ~f: - (Qsp_syntax.T.map_litteral ~f:(fun expr -> - Option.get (get expr_witness (Array.get expr i)))) - in - let value = S.Expression.literal pos values' in - R { value; witness = expr_witness }) - - let integer : S.pos -> string -> t = - fun pos value -> - Array.map A.t ~f:(fun (C.E { module_ = (module S); expr_witness; _ }) -> - let value = S.Expression.integer pos value in - R { value; witness = expr_witness }) + let build_ctx : result option array -> Qsp_syntax.S.extract_context = + fun results -> + { + f = + (fun id -> + Array.find_map results ~f:(function + | Some result -> get id result + | None -> None)); + } + + let literal : + ctx:Qsp_syntax.S.extract_context -> + S.pos -> + t Qsp_syntax.T.literal list -> + t = + fun ~ctx pos values -> + ignore ctx; + let results = Array.make len None in + (* Create the new array, filled with None at the begining. + + Then populate the array in place in order to read the previous values + if requested *) + (* Extract the result with the given ID from the array *) + let ctx = build_ctx results in + let () = + Array.mapi_inplace results ~f:(fun i _ -> + let (Qsp_syntax.Identifier.E + { module_ = (module S); expr_witness; _ }) = + Array.get checks i + in + (* Map every values to the Checker *) + let values' = + List.map values + ~f: + (Qsp_syntax.T.map_litteral ~f:(fun expr -> + Option.get (get expr_witness (Array.get expr i)))) + in + let value = S.Expression.literal ~ctx pos values' in + Some (R { value; witness = expr_witness })) + in + Array.map results ~f:Option.get - (** Unary operator like [-123] or [+'Text']*) - let uoperator : S.pos -> Qsp_syntax.T.uoperator -> t -> t = - fun pos op values -> - (* Evaluate the nested expression *) - let results = values in + let integer : ctx:Qsp_syntax.S.extract_context -> S.pos -> string -> t = + fun ~ctx pos value -> + ignore ctx; + let results = Array.make len None in + let ctx = build_ctx results in - (* Now evaluate the remaining expression. + let () = + Array.mapi_inplace results ~f:(fun i _ -> + let (Qsp_syntax.Identifier.E + { module_ = (module S); expr_witness; _ }) = + Array.get checks i + in + let value = S.Expression.integer ~ctx pos value in + Some (R { value; witness = expr_witness })) + in + Array.map results ~f:Option.get - Traverse both the module the apply, and the matching expression already - evaluated. + (** Unary operator like [-123] or [+'Text']*) + let uoperator : + ctx:Qsp_syntax.S.extract_context -> + S.pos -> + Qsp_syntax.T.uoperator -> + t -> + t = + fun ~ctx pos op values -> + ignore ctx; + let results = Array.make len None in + let ctx = build_ctx results in - It’s easer to use [map] and declare [report] as reference instead of - [fold_left2] and accumulate the report inside the closure, because I - don’t manage the order of the results. - *) - let results = - Array.map2 A.t results - ~f:(fun (C.E { module_ = (module S); expr_witness; _ }) value -> + (* Evaluate the nested expression *) + let () = + Array.mapi_inplace results ~f:(fun i _ -> + let (Qsp_syntax.Identifier.E + { module_ = (module S); expr_witness; _ }) = + Array.get checks i + in + let value = Array.get values i in match get expr_witness value with | None -> failwith "Does not match" | Some value -> (* Evaluate the single expression *) - let value = S.Expression.uoperator pos op value in - R { witness = expr_witness; value }) + let value = S.Expression.uoperator ~ctx pos op value in + Some (R { witness = expr_witness; value })) in - results + Array.map results ~f:Option.get (** Basically the same as uoperator, but operate over two operands instead of a single one. *) - let boperator : S.pos -> Qsp_syntax.T.boperator -> t -> t -> t = - fun pos op expr1 expr2 -> - Array.init len ~f:(fun i -> - let (E { module_ = (module S); expr_witness; _ }) = Array.get A.t i in - match - ( get expr_witness (Array.get expr1 i), - get expr_witness (Array.get expr2 i) ) - with - | Some value_1, Some value_2 -> - let value = S.Expression.boperator pos op value_1 value_2 in - R { witness = expr_witness; value } - | _ -> failwith "Does not match") + let boperator : + ctx:Qsp_syntax.S.extract_context -> + S.pos -> + Qsp_syntax.T.boperator -> + t -> + t -> + t = + fun ~ctx pos op expr1 expr2 -> + ignore ctx; + let results = Array.make len None in + let ctx = build_ctx results in + let () = + Array.mapi_inplace results ~f:(fun i _ -> + let (E { module_ = (module S); expr_witness; _ }) = + Array.get checks i + in + match + ( get expr_witness (Array.get expr1 i), + get expr_witness (Array.get expr2 i) ) + with + | Some value_1, Some value_2 -> + let value = + S.Expression.boperator ~ctx pos op value_1 value_2 + in + Some (R { witness = expr_witness; value }) + | _ -> failwith "Does not match") + in + Array.map results ~f:Option.get (** Call a function. The functions list is hardcoded in lib/lexer.mll *) - let function_ : S.pos -> Qsp_syntax.T.function_ -> t list -> t = - fun pos func args -> - Array.init len ~f:(fun i -> - let (E { module_ = (module S); expr_witness; _ }) = Array.get A.t i in - (* Extract the arguments for each module *) - let args_i = List.rev (Helper.expr_i args expr_witness i).values in - let value = S.Expression.function_ pos func args_i in - R { witness = expr_witness; value }) - - let ident : (S.pos, t) S.variable -> t = - fun { pos : S.pos; name : string; index : t option } -> - Array.init len ~f:(fun i -> - let (E { module_ = (module S); expr_witness; _ }) = Array.get A.t i in - - match index with - | None -> - (* Easest case, just return the plain ident *) - let value = S.Expression.ident { pos; name; index = None } in - R { witness = expr_witness; value } - | Some t -> ( - match get expr_witness (Array.get t i) with - | None -> failwith "Does not match" - | Some value_1 -> - let value = - S.Expression.ident { pos; name; index = Some value_1 } - in - R { witness = expr_witness; value })) + let function_ : + ctx:Qsp_syntax.S.extract_context -> + S.pos -> + Qsp_syntax.T.function_ -> + t list -> + t = + fun ~ctx pos func args -> + ignore ctx; + let results = Array.make len None in + let ctx = build_ctx results in + let () = + Array.mapi_inplace results ~f:(fun i _ -> + let (E { module_ = (module S); expr_witness; _ }) = + Array.get checks i + in + (* Extract the arguments for each module *) + let args_i = List.rev (Helper.expr_i args expr_witness i).values in + let value = S.Expression.function_ ~ctx pos func args_i in + Some (R { witness = expr_witness; value })) + in + Array.map results ~f:Option.get + + let ident : ctx:Qsp_syntax.S.extract_context -> (S.pos, t) S.variable -> t = + fun ~ctx { pos : S.pos; name : string; index : t option } -> + ignore ctx; + let results = Array.make len None in + let ctx = build_ctx results in + let () = + Array.mapi_inplace results ~f:(fun i _ -> + let (E { module_ = (module S); expr_witness; _ }) = + Array.get checks i + in + + match index with + | None -> + (* Easest case, just return the plain ident *) + let value = + S.Expression.ident ~ctx { pos; name; index = None } + in + Some (R { witness = expr_witness; value }) + | Some t -> ( + match get expr_witness (Array.get t i) with + | None -> failwith "Does not match" + | Some value_1 -> + let value = + S.Expression.ident ~ctx + { pos; name; index = Some value_1 } + in + Some (R { witness = expr_witness; value }))) + in + Array.map results ~f:Option.get (** Convert each internal represention for the expression into its external representation *) let v : t -> t' = fun t -> let result = - Array.map2 A.t t + Array.map2 checks t ~f:(fun - (C.E { module_ = (module S); expr_witness; expr'; _ }) result -> + (Qsp_syntax.Identifier.E + { module_ = (module S); expr_witness; expr'; _ }) + result + -> match get expr_witness result with | None -> failwith "Does not match" | Some value -> @@ -191,21 +339,30 @@ module Make (A : App) = struct let location : S.pos -> string -> t = fun pos label -> - Array.map A.t ~f:(fun (C.E { module_ = (module S); instr_witness; _ }) -> + Array.map checks + ~f:(fun + (Qsp_syntax.Identifier.E { module_ = (module S); instr_witness; _ }) + -> let value = S.Instruction.location pos label in R { value; witness = instr_witness }) let comment : S.pos -> t = fun pos -> - Array.map A.t ~f:(fun (C.E { module_ = (module S); instr_witness; _ }) -> + Array.map checks + ~f:(fun + (Qsp_syntax.Identifier.E { module_ = (module S); instr_witness; _ }) + -> let value = S.Instruction.comment pos in R { value; witness = instr_witness }) let expression : expression -> t = fun expr -> - Array.map2 A.t expr + Array.map2 checks expr ~f:(fun - (C.E { module_ = (module S); instr_witness; expr'; _ }) result -> + (Qsp_syntax.Identifier.E + { module_ = (module S); instr_witness; expr'; _ }) + result + -> match get expr' result with | None -> failwith "Does not match" | Some value -> @@ -219,7 +376,7 @@ module Make (A : App) = struct actually the list of each expression in the differents modules. *) Array.init len ~f:(fun i -> let (E { module_ = (module S); expr'; instr_witness; _ }) = - Array.get A.t i + Array.get checks i in let values = List.rev (Helper.expr_i args expr' i).values in @@ -231,7 +388,7 @@ module Make (A : App) = struct fun pos ~label instructions -> Array.init len ~f:(fun i -> let (E { module_ = (module S); instr_witness; expr'; _ }) = - Array.get A.t i + Array.get checks i in let values = List.rev (Helper.expr_i instructions instr_witness i).values @@ -254,7 +411,7 @@ module Make (A : App) = struct fun pos { pos = var_pos; name; index } op expression -> Array.init len ~f:(fun i -> let (E { module_ = (module A); instr_witness; expr'; _ }) = - Array.get A.t i + Array.get checks i in let index_i = @@ -303,7 +460,7 @@ module Make (A : App) = struct in Array.init len ~f:(fun i -> let (E { module_ = (module A); instr_witness; expr'; _ }) = - Array.get A.t i + Array.get checks i in let clause = rebuild_clause i instr_witness expr' clause @@ -324,9 +481,12 @@ module Make (A : App) = struct let v : t -> t' = fun t -> let result = - Array.map2 A.t t + Array.map2 checks t ~f:(fun - (C.E { module_ = (module S); instr_witness; instr'; _ }) result -> + (Qsp_syntax.Identifier.E + { module_ = (module S); instr_witness; instr'; _ }) + result + -> match get instr_witness result with | None -> failwith "Does not match" | Some value -> @@ -358,7 +518,7 @@ module Make (A : App) = struct context; _; }) = - Array.get A.t i + Array.get checks i in let local_context = @@ -377,7 +537,7 @@ module Make (A : App) = struct let () = Array.iteri args ~f:(fun i result -> let (E { module_ = (module A); location_witness; _ }) = - Array.get A.t i + Array.get checks i in match get location_witness result with | None -> failwith "Does not match" diff --git a/lib/checks/check.mli b/lib/checks/check.mli index 8502753..34d953f 100644 --- a/lib/checks/check.mli +++ b/lib/checks/check.mli @@ -13,17 +13,20 @@ end) ]} *) -val get_module : Qsp_syntax.Catalog.ex -> (module Qsp_syntax.S.Analyzer) - type result val get : 'a Type.Id.t -> result -> 'a option (** The method [get] can be used to get the internal value for one of the checker used. *) +val set : 'a Type.Id.t -> result -> 'a -> result option + module Make (A : sig - val t : Qsp_syntax.Catalog.ex array + val t : Qsp_syntax.Identifier.t array end) : sig - include Qsp_syntax.S.Analyzer with type Location.t = result array + include + Qsp_syntax.Analyzer.T + with type Location.t = result array + and type context = result array end [@@warning "-67"] diff --git a/lib/checks/compose.ml b/lib/checks/compose.ml deleted file mode 100644 index 4517755..0000000 --- a/lib/checks/compose.ml +++ /dev/null @@ -1,127 +0,0 @@ -(** Build a module with the result from another one module *) - -open StdLabels -module S = Qsp_syntax.S -module T = Qsp_syntax.T - -(** Make a module lazy *) -module Lazier (E : S.Expression) : - S.Expression with type t' = E.t' Lazy.t and type t = E.t Lazy.t = struct - type t = E.t Lazy.t - type t' = E.t' Lazy.t - - let v : E.t Lazy.t -> E.t' Lazy.t = Lazy.map E.v - let integer : S.pos -> string -> t = fun pos i -> lazy (E.integer pos i) - - let ident : (S.pos, t) S.variable -> t = - fun { pos; name : string; index : t option } -> - lazy (E.ident { pos; name; index = Option.map Lazy.force index }) - - let literal : S.pos -> t T.literal list -> t = - fun pos litts -> - lazy - (let e_litts = List.map litts ~f:(T.map_litteral ~f:Lazy.force) in - E.literal pos e_litts) - - let function_ : S.pos -> T.function_ -> t list -> t = - fun pos f e -> - lazy - (let e' = List.map ~f:Lazy.force e in - E.function_ pos f e') - - let uoperator : S.pos -> T.uoperator -> t -> t = - fun pos op t -> - let t' = lazy (E.uoperator pos op (Lazy.force t)) in - t' - - let boperator : S.pos -> T.boperator -> t -> t -> t = - fun pos op t1 t2 -> - let t' = lazy (E.boperator pos op (Lazy.force t1) (Lazy.force t2)) in - t' -end - -(** Build an expression module with the result from another expression. The - signature of the fuctions is a bit different, as they all receive the - result from the previous evaluated element in argument. *) -module Expression (E : S.Expression) = struct - module type SIG = sig - type t - type t' - - (* Override the type [t] in the definition of all the functions. The - signatures differs a bit from the standard signature as they get the - result from E.t in last argument *) - - val ident : (S.pos, E.t' Lazy.t * t) S.variable -> E.t' Lazy.t -> t - val integer : S.pos -> string -> E.t' Lazy.t -> t - val literal : S.pos -> (E.t' Lazy.t * t) T.literal list -> E.t' Lazy.t -> t - - val function_ : - S.pos -> T.function_ -> (E.t' Lazy.t * t) list -> E.t' Lazy.t -> t - - val uoperator : S.pos -> T.uoperator -> E.t' Lazy.t * t -> E.t' Lazy.t -> t - - val boperator : - S.pos -> - T.boperator -> - E.t' Lazy.t * t -> - E.t' Lazy.t * t -> - E.t' Lazy.t -> - t - - val v : E.t' Lazy.t * t -> t' - (** Convert from the internal representation to the external one. *) - end - - (* Create a lazy version of the module *) - module E = Lazier (E) - - module Make (M : SIG) : S.Expression with type t' = M.t' = struct - type t = E.t * M.t - type t' = M.t' - - let v' : E.t -> E.t' = E.v - let v : t -> t' = fun (type_of, v) -> M.v (v' type_of, v) - - let ident : (S.pos, t) S.variable -> t = - fun { pos; name : string; index : t option } -> - let t' = E.ident { pos; name; index = Option.map fst index } in - let index' = Option.map (fun (e, m) -> (v' e, m)) index in - (t', M.ident { pos; name; index = index' } (v' t')) - - let integer : S.pos -> string -> t = - fun pos i -> - let t' = E.integer pos i in - (t', M.integer pos i (v' t')) - - let literal : S.pos -> t T.literal list -> t = - fun pos litts -> - let litts' = - List.map litts ~f:(T.map_litteral ~f:(fun (e, m) -> (v' e, m))) - in - - let t' = - let e_litts = List.map litts ~f:(T.map_litteral ~f:fst) in - E.literal pos e_litts - in - (t', M.literal pos litts' (v' t')) - - let function_ : S.pos -> T.function_ -> t list -> t = - fun pos f expressions -> - let e = List.map ~f:fst expressions - and expressions' = List.map ~f:(fun (e, m) -> (v' e, m)) expressions in - - let t' = E.function_ pos f e in - (t', M.function_ pos f expressions' (v' t')) - - let uoperator : S.pos -> T.uoperator -> t -> t = - fun pos op (t, expr) -> - let t' = E.uoperator pos op t in - (t', M.uoperator pos op (v' t, expr) (v' t')) - - let boperator : S.pos -> T.boperator -> t -> t -> t = - fun pos op (t1, expr1) (t2, expr2) -> - let t' = E.boperator pos op t1 t2 in - (t', M.boperator pos op (v' t1, expr1) (v' t2, expr2) (v' t')) - end -end diff --git a/lib/checks/dead_end.ml b/lib/checks/dead_end.ml index 629a966..dd3e945 100644 --- a/lib/checks/dead_end.ml +++ b/lib/checks/dead_end.ml @@ -7,7 +7,9 @@ let identifier = "dead_end" let description = "Check for dead end in the code" let is_global = false let active = ref false +let depends = [] +type ex = Qsp_syntax.Identifier.t type context = unit let initialize = Fun.id @@ -40,10 +42,8 @@ module Instruction = struct (** For each instruction, return thoses two informations : - - the intruction contains at [gt] - - the last instruction is a [gt] - - *) + - the intruction contains at [gt] + - the last instruction is a [gt] *) let v : t -> t' = fun t -> t let default = @@ -73,7 +73,8 @@ module Instruction = struct (** Raw expression *) let expression : Expression.t' -> t = fun _ -> default - (** The content of a block is very linear, I only need to check the last element *) + (** The content of a block is very linear, I only need to check the last + element *) let check_block : S.pos -> t list -> t = fun pos instructions -> let last_element = diff --git a/lib/checks/dead_end.mli b/lib/checks/dead_end.mli index d8fe7d6..73ec86a 100644 --- a/lib/checks/dead_end.mli +++ b/lib/checks/dead_end.mli @@ -1,6 +1,5 @@ -(** Checker looking for the dead ends in the source. +(** Checker looking for the dead ends in the source. - A dead end is a state where the user does not have any action. - *) + A dead end is a state where the user does not have any action. *) -include Qsp_syntax.S.Analyzer +include Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t diff --git a/lib/checks/default.ml b/lib/checks/default.ml index a2b53f6..0ec1084 100644 --- a/lib/checks/default.ml +++ b/lib/checks/default.ml @@ -1,45 +1,138 @@ -(** Default implementation which does nothing. +(** Default implementation which does nothing. -This module is expected to be used when you only need to implement an analyze -over a limited part of the whole syntax. *) + This module is expected to be used when you only need to implement an + analyze over a limited part of the whole syntax. *) +open StdLabels module S = Qsp_syntax.S module T = Qsp_syntax.T module Report = Qsp_syntax.Report -module type T = sig +module Expression (T' : sig type t val default : t -end - -module Expression (T' : T) = struct - (** - Describe a variable, using the name in capitalized text, and an optionnal +end) = +struct + (** Describe a variable, using the name in capitalized text, and an optionnal index. - If missing, the index should be considered as [0]. - *) + If missing, the index should be considered as [0]. *) type t' = T'.t - let ident : (S.pos, T'.t) S.variable -> T'.t = fun _ -> T'.default + let ident : + ctx:Qsp_syntax.S.extract_context -> (S.pos, T'.t) S.variable -> T'.t = + fun ~ctx _ -> + ignore ctx; + T'.default (* Basic values, text, number… *) - let integer : S.pos -> string -> T'.t = fun _ _ -> T'.default - let literal : S.pos -> T'.t T.literal list -> T'.t = fun _ _ -> T'.default + let integer : ctx:Qsp_syntax.S.extract_context -> S.pos -> string -> T'.t = + fun ~ctx _ _ -> + ignore ctx; + T'.default + + let literal : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T'.t T.literal list -> T'.t = + fun ~ctx _ _ -> + ignore ctx; + T'.default (** Call a function. The functions list is hardcoded in lib/lexer.mll *) - let function_ : S.pos -> T.function_ -> T'.t list -> T'.t = - fun _ _ _ -> T'.default + let function_ : + ctx:Qsp_syntax.S.extract_context -> + S.pos -> + T.function_ -> + T'.t list -> + T'.t = + fun ~ctx _ _ _ -> + ignore ctx; + T'.default (** Unary operator like [-123] or [+'Text']*) - let uoperator : S.pos -> T.uoperator -> T'.t -> T'.t = fun _ _ _ -> T'.default + let uoperator : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.uoperator -> T'.t -> T'.t = + fun ~ctx _ _ _ -> + ignore ctx; + T'.default (** Binary operator, for a comparaison, or an operation *) - let boperator : S.pos -> T.boperator -> T'.t -> T'.t -> T'.t = - fun _ _ _ _ -> T'.default + let boperator : + ctx:Qsp_syntax.S.extract_context -> + S.pos -> + T.boperator -> + T'.t -> + T'.t -> + T'.t = + fun ~ctx _ _ _ _ -> + ignore ctx; + T'.default +end + +module Instruction (Expression : sig + type t' +end) (T : sig + type t + + val default : t + val fold : t Seq.t -> t +end) = +struct + let call : S.pos -> Qsp_syntax.T.keywords -> Expression.t' list -> T.t = + fun _ _ _ -> T.default + + let location : S.pos -> string -> T.t = + fun position name -> + ignore position; + ignore name; + T.default + + let comment : S.pos -> T.t = + fun position -> + ignore position; + T.default + + let expression : Expression.t' -> T.t = + fun expr -> + ignore expr; + T.default + + let map_clause : (Expression.t', T.t) S.clause -> T.t Seq.t = + fun (_, _, els) -> List.to_seq els + + let if_ : + S.pos -> + (Expression.t', T.t) S.clause -> + elifs:(Expression.t', T.t) S.clause list -> + else_:(S.pos * T.t list) option -> + T.t = + fun pos clause ~elifs ~else_ -> + ignore pos; + + let seq = List.to_seq (clause :: elifs) |> Seq.flat_map map_clause in + + let seq = + match else_ with + | None -> seq + | Some (_, ts) -> Seq.append seq (List.to_seq ts) + in + T.fold seq + + let act : S.pos -> label:Expression.t' -> T.t list -> T.t = + fun pos ~label instructions -> + ignore pos; + ignore label; + T.fold (List.to_seq instructions) + + let assign : + S.pos -> + (S.pos, Expression.t') S.variable -> + Qsp_syntax.T.assignation_operator -> + Expression.t' -> + T.t = + fun _ _ _ _ -> T.default end diff --git a/lib/checks/dune b/lib/checks/dune index d7db2f3..75b311b 100644 --- a/lib/checks/dune +++ b/lib/checks/dune @@ -1,9 +1,12 @@ (library (name qsp_checks) (libraries + tsort qsp_syntax ) (preprocess (pps - ppx_deriving.show ppx_deriving.enum + ppx_deriving.show + ppx_deriving.enum + ppx_deriving.ord ppx_deriving.eq ))) diff --git a/lib/checks/dup_test.ml b/lib/checks/dup_test.ml index 9ffe7c5..4de9a4d 100644 --- a/lib/checks/dup_test.ml +++ b/lib/checks/dup_test.ml @@ -1,9 +1,7 @@ (** This module check for duplicated tests in the source.contents - - This in intended to identify the copy/paste errors, where one location - check for the same arguments twice or more. - *) + This in intended to identify the copy/paste errors, where one location check + for the same arguments twice or more. *) open StdLabels module S = Qsp_syntax.S @@ -15,7 +13,9 @@ let identifier = "duplicate_test" let description = "Check for duplicate tests" let is_global = false let active = ref true +let depends = [] +type ex = Qsp_syntax.Identifier.t type context = unit let initialize = Fun.id @@ -23,8 +23,8 @@ let finalize () = [] module Expression = Tree.Expression -(** Build a Hashtbl over the expression, ignoring the location in the - expression *) +(** Build a Hashtbl over the expression, ignoring the location in the expression +*) module Table = Hashtbl.Make (struct type t = Expression.t' @@ -37,23 +37,33 @@ module Instruction = struct predicates : (Expression.t' * S.pos) list; duplicates : (Expression.t' * S.pos list) list; } - (** Keep the list of all the predicates and their position in a block, and - the list of all the identified duplicated values. *) + (** Keep the list of all the predicates and their position in a block, and the + list of all the identified duplicated values. *) type t = state type t' = state - let v : t -> t' = fun t -> t let default = { predicates = []; duplicates = [] } - (** Label for a loop *) - let location : S.pos -> string -> t = fun _ _ -> default + include + Default.Instruction + (Expression) + (struct + type nonrec t = t - (** Comment *) - let comment : S.pos -> t = fun _ -> default + let default = default - (** Raw expression *) - let expression : Expression.t' -> t = fun _ -> default + let fold sequence = + Seq.fold_left + (fun state ex -> + { + predicates = []; + duplicates = List.rev_append ex.duplicates state.duplicates; + }) + default sequence + end) + + let v : t -> t' = fun t -> t let check_duplicates : (Expression.t' * S.pos) list -> (Expression.t' * S.pos list) list = @@ -74,10 +84,9 @@ module Instruction = struct | other -> Some (hd, other))) |> List.of_seq - (** Evaluate a clause. - This function does two things : - - report all errors from the bottom to top - - add the clause in the actual level *) + (** Evaluate a clause. This function does two things : + - report all errors from the bottom to top + - add the clause in the actual level *) let predicate_of_clause : ?pos:S.pos -> t -> (Expression.t', t) S.clause -> t = fun ?pos t (pos2, predicate, blocks) -> @@ -118,27 +127,6 @@ module Instruction = struct state with duplicates = check_duplicates state.predicates @ state.duplicates; } - - let act : S.pos -> label:Expression.t' -> t list -> t = - fun _pos ~label expressions -> - ignore label; - (* Collect all the elements reported from bottom to up. *) - List.fold_left ~init:default expressions ~f:(fun state ex -> - { - predicates = []; - duplicates = List.rev_append ex.duplicates state.duplicates; - }) - - let assign : - S.pos -> - (S.pos, Expression.t') S.variable -> - T.assignation_operator -> - Expression.t' -> - t = - fun _ _ _ _ -> default - - let call : S.pos -> T.keywords -> Expression.t' list -> t = - fun _ _ _ -> default end module Location = struct diff --git a/lib/checks/dup_test.mli b/lib/checks/dup_test.mli index 6446c67..a771a46 100644 --- a/lib/checks/dup_test.mli +++ b/lib/checks/dup_test.mli @@ -1 +1 @@ -include Qsp_syntax.S.Analyzer +include Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t diff --git a/lib/checks/dynamics.ml b/lib/checks/dynamics.ml new file mode 100644 index 0000000..f88550b --- /dev/null +++ b/lib/checks/dynamics.ml @@ -0,0 +1,269 @@ +open StdLabels +module S = Qsp_syntax.S +module T = Qsp_syntax.T +module Report = Qsp_syntax.Report + +let identifier = "dynamics" +let description = "Report all dynamics string in the module" +let is_global = true +let active = ref false +let depends = [] + +type ex = Qsp_syntax.Identifier.t +type text = { content : string; position : S.pos } [@@deriving eq, ord] + +module StringSet = Set.Make (struct + type t = text [@@deriving ord] +end) + +type context = StringSet.t ref + +let initialize () = ref StringSet.empty + +module Expression = struct + (** Elements wich can be given to dynamic. + + For Text, I do not evaluate text containing expression. This need to be a + plain text. + + In the case of variable, indexes will probably not work if they include + function or complex expression *) + type t = None | Text of text | Variable of (unit, t) S.variable + [@@deriving eq, ord] + + (** Remove all the locations inside a variable in order to be able to compare + two of them at differents locations *) + let rec anonymize_variable : (unit, t) S.variable -> (unit, t) S.variable = + fun ({ index; _ } as variable) -> + let index = + Option.map + (function + | None -> None + | Text { content; _ } -> + let position = (Lexing.dummy_pos, Lexing.dummy_pos) in + Text { content; position } + | Variable var -> Variable (anonymize_variable var)) + index + in + { variable with index } + + include Default.Expression (struct + type nonrec t = t + + let default = None + end) + + let v : t -> t' = Fun.id + + (** Only keep the raw strings *) + let literal : + ctx:Qsp_syntax.S.extract_context -> S.pos -> t T.literal list -> t = + fun ~ctx position content -> + ignore ctx; + ignore position; + match content with + | [ T.Text content ] -> Text { content; position } + | _ -> ( + (* Here I analyse if the expression is a string or + numeric. In case of numeric, it is possible to replace it with a + default value *) + let buffer = Buffer.create 16 in + let res = + List.fold_left ~init:`Ok content ~f:(fun state literal -> + match (state, literal) with + | `None, _ -> `None + | `Ok, T.Expression None -> `None + | `Ok, T.Expression (Text content) -> + Buffer.add_string buffer content.content; + `Ok + | `Ok, T.Text content -> + Buffer.add_string buffer content; + `Ok + | `Ok, T.Expression (Variable { name; _ }) -> + let res = + if Char.equal '$' name.[0] then `None + else ( + Buffer.add_char buffer '0'; + `Ok) + in + res) + in + match res with + | `Ok -> Text { content = Buffer.contents buffer; position } + | _ -> None) + + (** Consider the integer as text. This is easier for evaluating the indices in + the arrays (it use the same code as text indices), and will report bad use + of dynamics. *) + let integer : ctx:Qsp_syntax.S.extract_context -> S.pos -> string -> t = + fun ~ctx position content -> + ignore ctx; + Text { content; position } + + (** If the identifier uses any unmanaged expression in the indices, ignore it. + *) + let ident : ctx:Qsp_syntax.S.extract_context -> (S.pos, t) S.variable -> t = + fun ~ctx ({ index; _ } as ident) -> + ignore ctx; + let is_valid = + Option.fold ~none:true index ~some:(fun opt -> + match opt with None -> false | _ -> true) + in + match is_valid with + | false -> None + | true -> Variable (anonymize_variable { ident with pos = () }) +end + +module Instruction = struct + (** This map holds the values for each variable seen in the code *) + module StringMap = struct + include Hashtbl.Make (struct + type t = (unit, Expression.t) S.variable [@@deriving eq] + + let hash = Hashtbl.hash + end) + + (** Recursive search in the table *) + let rec_find : + Expression.t' t -> (unit, Expression.t) S.variable -> StringSet.t = + fun table key -> + let rec _f init key = + let values = find_all table key in + List.fold_left values ~init ~f:(fun acc value -> + match value with + | Expression.None -> acc + | Expression.Text text -> StringSet.add text acc + | Expression.Variable variable -> _f acc variable) + in + _f StringSet.empty key + end + + module VariableSet = Set.Make (struct + type t = (unit, Expression.t) S.variable [@@deriving ord] + end) + + type context = { + catalog : Expression.t' StringMap.t; + texts : StringSet.t; + blacklist : VariableSet.t; + variable_called : VariableSet.t; + } + (** Keep the content of each string in order to parse it later *) + + (** This module do two things : keep a track of the raw strings in the + location, and identify the calls to the function dynamic. + + The dynamic parameter are reported as is, and are evaluated only at the + end of the module. *) + + type t = context -> context + type t' = t + + let v = Fun.id + + include + Default.Instruction + (Expression) + (struct + type nonrec t = t + + let fold : t Seq.t -> t = + fun seq init_context -> + let result = + Seq.fold_left + (fun context (instr : t) -> instr context) + init_context seq + in + result + + let default context = context + end) + + (** Keep the track of dynamic instructions *) + let call : S.pos -> T.keywords -> Expression.t' list -> t = + fun position keyword arg context -> + ignore position; + ignore arg; + match keyword with + | T.Dynamic -> ( + match arg with + | [ Expression.Text text ] -> + let texts = StringSet.add text context.texts in + + { context with texts } + | [ Expression.Variable var ] -> + let variable_called = VariableSet.add var context.variable_called in + { context with variable_called } + | _ -> context) + | _ -> context + + let assign : + S.pos -> + (S.pos, Expression.t') S.variable -> + T.assignation_operator -> + Expression.t' -> + t = + fun pos variable op expression context -> + ignore pos; + let variable' = Expression.anonymize_variable { variable with pos = () } in + let is_blacklisted = VariableSet.mem variable' context.blacklist in + let is_string = variable.name.[0] = '$' in + match (op, expression, is_blacklisted, is_string) with + | T.Eq', Expression.Text content, false, true + when not (String.equal content.content "") -> + StringMap.add context.catalog variable' expression; + context + | T.Eq', Expression.Variable _, false, _ -> + StringMap.add context.catalog variable' expression; + context + | _ -> + (* If the assignation is not direct, we **remove** all the bindings + from the catalog. *) + StringMap.find_all context.catalog variable' + |> List.iter ~f:(fun _ -> StringMap.remove context.catalog variable'); + + (* We also black list this variable and prevent further additions *) + let blacklist = VariableSet.add variable' context.blacklist in + { context with blacklist } +end + +module Location = struct + type t = unit + type instruction = Instruction.t' + + let location : context -> S.pos -> instruction list -> t = + fun context pos instr -> + ignore pos; + let catalog = Instruction.StringMap.create 16 in + let init = + Instruction. + { + catalog; + texts = !context; + blacklist = VariableSet.empty; + variable_called = VariableSet.empty; + } + in + let res = List.fold_left instr ~init ~f:(fun acc instr -> instr acc) in + + (* Now, for each dynamics calling a variable, looks in the catalog if we + can find the associated string *) + let texts = + Instruction.VariableSet.fold + (fun variable acc -> + let indirects = Instruction.StringMap.rec_find res.catalog variable in + + StringSet.union acc indirects) + res.variable_called res.texts + in + context := texts + + let v : t -> Report.t list = fun _ -> [] +end + +let finalize context = + ignore context; + [] + +let dynamics_string : context -> text Seq.t = + fun context -> StringSet.to_seq !context diff --git a/lib/checks/dynamics.mli b/lib/checks/dynamics.mli new file mode 100644 index 0000000..588a05e --- /dev/null +++ b/lib/checks/dynamics.mli @@ -0,0 +1,5 @@ +include Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t + +type text = { content : string; position : Qsp_syntax.S.pos } + +val dynamics_string : context -> text Seq.t diff --git a/lib/checks/get_type.ml b/lib/checks/get_type.ml index 04bf780..00270c2 100644 --- a/lib/checks/get_type.ml +++ b/lib/checks/get_type.ml @@ -17,116 +17,183 @@ type type_of = (** String containing a numeric value *) [@@deriving show { with_path = false }, eq] -type t = Variable of type_of | Raw of type_of [@@deriving show, eq] -type t' = t - -let v = Fun.id -let get_type : t -> type_of = function Raw r -> r | Variable r -> r - -let map : t -> type_of -> t = - fun t type_of -> - match t with Raw _ -> Raw type_of | Variable _ -> Variable type_of - -let get_nature : t -> t -> type_of -> t = - fun t1 t2 type_of -> - match (t1, t2) with - | Variable _, _ -> Variable type_of - | _, Variable _ -> Variable type_of - | Raw _, Raw _ -> Raw type_of - -let integer : S.pos -> string -> t = fun _ _ -> Raw Integer - -let ident : (S.pos, 'any) S.variable -> t = - fun var -> - match var.name.[0] with '$' -> Variable String | _ -> Variable Integer - -let literal : S.pos -> t T.literal list -> t = - fun pos values -> - ignore pos; - let init = None in - let typed = - List.fold_left values ~init ~f:(fun state -> function - | T.Text t -> ( - (* Tranform the type, but keep the information is it’s a raw data +module Expression = struct + type t = Variable of type_of | Raw of type_of [@@deriving show, eq] + type t' = t + + let v = Fun.id + let get_type : t -> type_of = function Raw r -> r | Variable r -> r + + let map : t -> type_of -> t = + fun t type_of -> + match t with Raw _ -> Raw type_of | Variable _ -> Variable type_of + + let get_nature : t -> t -> type_of -> t = + fun t1 t2 type_of -> + match (t1, t2) with + | Variable _, _ -> Variable type_of + | _, Variable _ -> Variable type_of + | Raw _, Raw _ -> Raw type_of + + let integer : ctx:Qsp_syntax.S.extract_context -> S.pos -> string -> t = + fun ~ctx _ _ -> + ignore ctx; + Raw Integer + + let ident : ctx:Qsp_syntax.S.extract_context -> (S.pos, 'any) S.variable -> t + = + fun ~ctx var -> + ignore ctx; + match var.name.[0] with '$' -> Variable String | _ -> Variable Integer + + let literal : + ctx:Qsp_syntax.S.extract_context -> S.pos -> t T.literal list -> t = + fun ~ctx pos values -> + ignore ctx; + ignore pos; + let init = None in + let typed = + List.fold_left values ~init ~f:(fun state -> function + | T.Text t -> ( + (* Tranform the type, but keep the information is it’s a raw data or a variable one *) - let nature = Option.value ~default:(Raw Integer) state in - match (Option.map get_type state, int_of_string_opt t) with - | None, Some _ - | Some Integer, Some _ - | Some NumericString, Some _ - | Some Bool, Some _ -> - Some (map nature NumericString) - | _, _ -> - if String.equal "" t then - (* If the text is empty, ignore it *) - state - else Some (map nature String)) - | T.Expression t -> ( - let nature = Option.value ~default:(Raw Integer) state in - match (Option.map get_type state, get_type t) with - | None, Integer | Some NumericString, Integer -> - Some (get_nature nature t NumericString) - | _ -> Some (map nature String))) - in - let result = Option.value ~default:(Raw String) typed in - result - -let uoperator : S.pos -> T.uoperator -> t -> t = - fun pos operator t -> - ignore pos; - match operator with Add -> t | Neg | No -> Raw Integer - -let boperator : S.pos -> T.boperator -> t -> t -> t = - fun pos operator t1 t2 -> - ignore pos; - match operator with - | T.Plus -> ( - match (get_type t1, get_type t2) with - | Integer, Integer -> get_nature t1 t2 Integer - | String, _ -> get_nature t1 t2 String - | _, String -> get_nature t1 t2 String - | (_ as t), Bool -> get_nature t1 t2 t - | Bool, (_ as t) -> get_nature t1 t2 t - | (_ as t), NumericString -> get_nature t1 t2 t - | NumericString, (_ as t) -> get_nature t1 t2 t) - | T.Eq | T.Neq -> get_nature t1 t2 Bool - | T.Mod | T.Minus | T.Product | T.Div -> get_nature t1 t2 Integer - | T.And | T.Or -> get_nature t1 t2 Bool - | Lt | Gte | Lte | Gt -> get_nature t1 t2 Bool - -let function_ : S.pos -> T.function_ -> t list -> t = - fun pos function_ params -> - ignore pos; - match function_ with - | Dyneval | Dyneval' -> Variable NumericString - | Arrcomp | Arrpos | Arrsize | Countobj | Desc | Getobj | Instr | Isplay -> - Variable Integer - | Desc' | Getobj' -> Variable String - | Func | Func' -> Variable NumericString - | Iif | Iif' -> ( match params with _ :: t :: _ -> t | _ -> Raw Bool) - | Input | Input' -> Variable NumericString - | Isnum -> Raw Bool - | Lcase | Lcase' | Ucase | Ucase' -> Raw String - | Len -> Raw Integer - | Loc -> Variable Bool - | Max | Max' | Min | Min' -> ( - match params with - | [] -> Raw Bool - | Raw String :: [] | Variable String :: [] -> Variable NumericString - | hd :: _ -> hd) - | Mid | Mid' -> Variable String - | Msecscount -> Raw Integer - | Rand -> Raw Integer - | Replace -> Variable String - | Replace' -> Variable String - | Rgb -> Raw Integer - | Rnd -> Raw Integer - | Selact -> Variable String - | Str | Str' -> Raw String - | Strcomp -> Raw Bool - | Strfind -> Variable String - | Strfind' -> Variable String - | Strpos -> Raw Integer - | Trim -> Variable String - | Trim' -> Variable String - | Val -> Raw Integer + let nature = Option.value ~default:(Raw Integer) state in + match (Option.map get_type state, int_of_string_opt t) with + | None, Some _ + | Some Integer, Some _ + | Some NumericString, Some _ + | Some Bool, Some _ -> + Some (map nature NumericString) + | _, _ -> + if String.equal "" t then + (* If the text is empty, ignore it *) + state + else Some (map nature String)) + | T.Expression t -> ( + let nature = Option.value ~default:(Raw Integer) state in + match (Option.map get_type state, get_type t) with + | None, Integer | Some NumericString, Integer -> + Some (get_nature nature t NumericString) + | _ -> Some (map nature String))) + in + let result = Option.value ~default:(Raw String) typed in + result + + let uoperator : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.uoperator -> t -> t = + fun ~ctx pos operator t -> + ignore ctx; + ignore pos; + match operator with Add -> t | Neg | No -> Raw Integer + + let boperator : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.boperator -> t -> t -> t = + fun ~ctx pos operator t1 t2 -> + ignore ctx; + ignore pos; + match operator with + | T.Plus -> ( + match (get_type t1, get_type t2) with + | Integer, Integer -> get_nature t1 t2 Integer + | String, _ -> get_nature t1 t2 String + | _, String -> get_nature t1 t2 String + | (_ as t), Bool -> get_nature t1 t2 t + | Bool, (_ as t) -> get_nature t1 t2 t + | (_ as t), NumericString -> get_nature t1 t2 t + | NumericString, (_ as t) -> get_nature t1 t2 t) + | T.Eq | T.Neq -> get_nature t1 t2 Bool + | T.Mod | T.Minus | T.Product | T.Div -> get_nature t1 t2 Integer + | T.And | T.Or -> get_nature t1 t2 Bool + | Lt | Gte | Lte | Gt -> get_nature t1 t2 Bool + + let function_ : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.function_ -> t list -> t = + fun ~ctx pos function_ params -> + ignore ctx; + ignore pos; + match function_ with + | Dyneval | Dyneval' -> Variable NumericString + | Arrcomp | Arrpos | Arrsize | Countobj | Desc | Getobj | Instr | Isplay -> + Variable Integer + | Desc' | Getobj' -> Variable String + | Func | Func' -> Variable NumericString + | Iif | Iif' -> ( match params with _ :: t :: _ -> t | _ -> Raw Bool) + | Input | Input' -> Variable NumericString + | Isnum -> Raw Bool + | Lcase | Lcase' | Ucase | Ucase' -> Raw String + | Len -> Variable Integer + | Loc -> Variable Bool + | Max | Max' | Min | Min' -> ( + match params with + | [] -> Raw Bool + | Raw String :: [] | Variable String :: [] -> Variable NumericString + | hd :: _ -> hd) + | Mid | Mid' -> Variable String + | Msecscount -> Variable Integer + | Rand -> Variable Integer + | Replace -> Variable String + | Replace' -> Variable String + | Rgb -> Variable Integer + | Rnd -> Variable Integer + | Selact -> Variable String + | Str | Str' -> Raw String + | Strcomp -> Raw Bool + | Strfind -> Variable String + | Strfind' -> Variable String + | Strpos -> Variable Integer + | Trim -> Variable String + | Trim' -> Variable String + | Val -> Variable Integer +end + +module A = struct + let identifier = "get_types" + let description = "Identify the type for an expression" + let is_global = true + let active = ref false + let depends = [] + + type ex = Qsp_syntax.Identifier.t + type context = unit + + let initialize () = () + + module Expression = Expression + + module Instruction = struct + type t = unit + type t' = unit + + include + Default.Instruction + (Expression) + (struct + type t = unit + + let default = () + let fold seq = Seq.iter (fun _ -> ()) seq + end) + + let v = Fun.id + end + + module Location = struct + type t = unit + type instruction = Instruction.t' + + let location : context -> S.pos -> instruction list -> t = + fun context pos instr -> + ignore context; + ignore pos; + List.iter instr ~f:(fun _ -> ()) + + let v : t -> Report.t list = fun _ -> [] + end + + let finalize context = + ignore context; + [] +end + +let expression_id = Type.Id.make () +let ex = Qsp_syntax.Identifier.build ~expression_id (module A) diff --git a/lib/checks/get_type.mli b/lib/checks/get_type.mli new file mode 100644 index 0000000..476059b --- /dev/null +++ b/lib/checks/get_type.mli @@ -0,0 +1,25 @@ +type type_of = + | Integer (** A numeric value *) + | Bool (** A boolean, not a real type *) + | String (** String value *) + | NumericString (** String containing a numeric value *) +[@@deriving show, eq] + +module Expression : sig + type t = Variable of type_of | Raw of type_of [@@deriving show, eq] + type t' = t + + include Qsp_syntax.S.Expression with type t := t and type t' := t' + + val ident : + ctx:Qsp_syntax.S.extract_context -> + (Qsp_syntax.S.pos, 'any) Qsp_syntax.S.variable -> + t + + val get_type : t -> type_of +end + +val expression_id : Expression.t Type.Id.t +(** Type identifier for the expression in this module *) + +val ex : Qsp_syntax.Identifier.t diff --git a/lib/checks/locations.ml b/lib/checks/locations.ml index 8ee6ffa..3a5ddf5 100644 --- a/lib/checks/locations.ml +++ b/lib/checks/locations.ml @@ -20,6 +20,9 @@ let identifier = "locations" let description = "Ensure every call points to an existing location" let is_global = true let active = ref true +let depends = [] + +type ex = Qsp_syntax.Identifier.t type t = { locations : LocationSet.t; @@ -74,7 +77,7 @@ let registerLocation : string -> t -> t = { calls; locations } (** The module Expression is pretty simple, we are only interrested by the - strings ( because only the first argument of [gt …] is read ). + strings ( because only the first argument of [gt …] is read ). If the string is too much complex, we just ignore it. *) module Expression = struct @@ -89,8 +92,11 @@ module Expression = struct let v : t -> t' = Fun.id (* Extract the litteral if this is a simple text *) - let literal : S.pos -> t' T.literal list -> t' = - fun _ ll -> match ll with Text lit :: [] -> Some lit | _ -> None + let literal : + ctx:Qsp_syntax.S.extract_context -> S.pos -> t' T.literal list -> t' = + fun ~ctx _ ll -> + ignore ctx; + match ll with Text lit :: [] -> Some lit | _ -> None end module Instruction = struct @@ -99,6 +105,18 @@ module Instruction = struct let v : t -> t' = Fun.id + include + Default.Instruction + (Expression) + (struct + type nonrec t = t + + let default = Fun.id + + let fold : t Seq.t -> t = + fun sequence t -> Seq.fold_left (fun acc t -> t acc) t sequence + end) + (** Keep a track of every gt or gs instruction *) let call : S.pos -> T.keywords -> Expression.t' list -> t = fun pos fn args t -> @@ -106,43 +124,6 @@ module Instruction = struct | T.Goto, Some dest :: _ -> registerCall pos dest t | T.Gosub, Some dest :: _ -> registerCall pos dest t | _ -> t - - let location : S.pos -> string -> t = fun _ _ -> Fun.id - let comment : S.pos -> t = fun _ -> Fun.id - let expression : Expression.t' -> t = fun _ -> Fun.id - - let if_ : - S.pos -> - (Expression.t', t) S.clause -> - elifs:(Expression.t', t) S.clause list -> - else_:(S.pos * t list) option -> - t = - fun _ clause ~elifs ~else_ t -> - let traverse_clause t clause = - let _, _, block = clause in - List.fold_left block ~init:t ~f:(fun t instruction -> instruction t) - in - - let t = traverse_clause t clause in - let t = List.fold_left ~init:t ~f:traverse_clause elifs in - match else_ with - | None -> t - | Some (_, instructions) -> - List.fold_left instructions ~init:t ~f:(fun t instruction -> - instruction t) - - let act : S.pos -> label:Expression.t' -> t list -> t = - fun _ ~label instructions t -> - ignore label; - List.fold_left instructions ~init:t ~f:(fun t instruction -> instruction t) - - let assign : - S.pos -> - (S.pos, Expression.t') S.variable -> - T.assignation_operator -> - Expression.t' -> - t = - fun _ _ _ _ -> Fun.id end module Location = struct diff --git a/lib/checks/nested_strings.ml b/lib/checks/nested_strings.ml index e4ffb68..d4a7947 100644 --- a/lib/checks/nested_strings.ml +++ b/lib/checks/nested_strings.ml @@ -7,82 +7,77 @@ let identifier = "escaped_string" let description = "Check for unnecessary use of expression encoded in string" let is_global = false let active = ref true +let depends = [ Get_type.ex ] +type ex = Qsp_syntax.Identifier.t type context = unit let initialize = Fun.id let finalize () = [] -module TypeBuilder = Compose.Expression (Get_type) - -module Expression = TypeBuilder.Make (struct - type t = Report.t list +module Expression = struct + type t = { type_of : Get_type.Expression.t; report : Report.t list } type t' = Report.t list - let v : Get_type.t Lazy.t * t -> t' = snd + let v : t -> t' = fun t -> t.report (** Identify the expressions reprented as string. That’s here that the report - are added. + are added. All the rest of the module only push thoses warning to the top level. *) let literal : - S.pos -> (Get_type.t Lazy.t * t) T.literal list -> Get_type.t Lazy.t -> t - = - fun pos content _type_of -> + ctx:Qsp_syntax.S.extract_context -> S.pos -> t T.literal list -> t = + fun ~ctx pos content -> + let type_of = Option.get (ctx.f Get_type.expression_id) in match content with - | [ T.Expression (t', _); T.Text "" ] -> ( - match Get_type.get_type (Lazy.force t') with - | Get_type.Integer -> [] + | [ T.Expression t; T.Text "" ] -> ( + match Get_type.Expression.get_type t.type_of with + | Get_type.Integer -> { type_of; report = [] } | _ -> let msg = Report.debug pos "This expression can be simplified" in - [ msg ]) - | _ -> [] + { type_of; report = [ msg ] }) + | _ -> { type_of; report = [] } - let ident : - (S.pos, Get_type.t Lazy.t * t) S.variable -> Get_type.t Lazy.t -> t = - fun variable _type_of -> - match variable.index with None -> [] | Some (_, t) -> t + let ident : ctx:Qsp_syntax.S.extract_context -> (S.pos, t) S.variable -> t = + fun ~ctx variable -> + let type_of = Option.get (ctx.f Get_type.expression_id) in + match variable.index with None -> { type_of; report = [] } | Some t -> t - let integer : S.pos -> string -> Get_type.t Lazy.t -> t = - fun pos t _type_of -> + let integer : ctx:Qsp_syntax.S.extract_context -> S.pos -> string -> t = + fun ~ctx pos t -> ignore pos; ignore t; - [] + let type_of = Option.get (ctx.f Get_type.expression_id) in + { type_of; report = [] } let function_ : - S.pos -> - T.function_ -> - (Get_type.t Lazy.t * t) list -> - Get_type.t Lazy.t -> - t = - fun pos f expressions _type_of -> + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.function_ -> t list -> t = + fun ~ctx pos f expressions -> + let type_of = Option.get (ctx.f Get_type.expression_id) in ignore pos; ignore f; let exprs = List.fold_left ~init:[] expressions ~f:(fun acc el -> - List.rev_append (snd el) acc) + List.rev_append el.report acc) in - exprs + { type_of; report = exprs } let uoperator : - S.pos -> T.uoperator -> Get_type.t Lazy.t * t -> Get_type.t Lazy.t -> t = - fun pos op r _type_of -> + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.uoperator -> t -> t = + fun ~ctx pos op r -> + let type_of = Option.get (ctx.f Get_type.expression_id) in ignore op; ignore pos; - snd r + { r with type_of } let boperator : - S.pos -> - T.boperator -> - Get_type.t Lazy.t * t -> - Get_type.t Lazy.t * t -> - Get_type.t Lazy.t -> - t = - fun pos op (_, r1) (_, r2) _type_of -> + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.boperator -> t -> t -> t = + fun ~ctx pos op r1 r2 -> + let type_of = Option.get (ctx.f Get_type.expression_id) in ignore pos; ignore op; - r1 @ r2 -end) + { type_of; report = r1.report @ r2.report } +end module Instruction : S.Instruction with type t' = Report.t list and type expression = Expression.t' = diff --git a/lib/checks/nested_strings.mli b/lib/checks/nested_strings.mli index 1ef2e33..01e373a 100644 --- a/lib/checks/nested_strings.mli +++ b/lib/checks/nested_strings.mli @@ -1,3 +1,3 @@ -include Qsp_syntax.S.Analyzer +include Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t (** The module [Nested_strings] report errors for each unnecessary raw string encoded inside a string expression *) diff --git a/lib/checks/type_of.ml b/lib/checks/type_of.ml index 70ae324..243c8b3 100644 --- a/lib/checks/type_of.ml +++ b/lib/checks/type_of.ml @@ -12,16 +12,19 @@ type context = unit let initialize = Fun.id let finalize () = [] +let depends = [ Get_type.ex ] + +type ex = Qsp_syntax.Identifier.t module Helper = struct - type argument_repr = { pos : S.pos; t : Get_type.t } + type argument_repr = { pos : S.pos; t : Get_type.Expression.t } module DynType = struct - type nonrec t = Get_type.t -> Get_type.t + type nonrec t = Get_type.Expression.t -> Get_type.Expression.t (** Dynamic type is a type unknown during the code. - For example, the equality operator accept either Integer or String, but - we expect that both sides of the equality uses the same type.*) + For example, the equality operator accept either Integer or String, but + we expect that both sides of the equality uses the same type.*) (** Build a new dynamic type *) let t : unit -> t = @@ -35,11 +38,11 @@ module Helper = struct | Some t -> t end - (** Declare an argument for a function. + (** Declare an argument for a function. - - Either we already know the type and we just have to compare. - - Either the type shall constrained by another one - - Or we have a variable number of arguments. *) + - Either we already know the type and we just have to compare. + - Either the type shall constrained by another one + - Or we have a variable number of arguments. *) type argument = | Fixed of Get_type.type_of | Dynamic of DynType.t @@ -143,37 +146,35 @@ module Helper = struct msg :: report end -module TypeBuilder = Compose.Expression (Get_type) - -type t' = { result : Get_type.t Lazy.t; pos : S.pos } +type t' = { result : Get_type.Expression.t; pos : S.pos } -let arg_of_repr : Get_type.t Lazy.t -> S.pos -> Helper.argument_repr = - fun type_of pos -> { pos; t = Lazy.force type_of } +let arg_of_repr : Get_type.Expression.t -> S.pos -> Helper.argument_repr = + fun type_of pos -> { pos; t = type_of } -module TypedExpression = struct +module Expression = struct type nonrec t' = t' * Report.t list - type state = { pos : S.pos } + type state = { pos : S.pos; type_of : Get_type.Expression.t } type t = state * Report.t list - let v : Get_type.t Lazy.t * t -> t' = - fun (type_of, (t, r)) -> ({ result = type_of; pos = t.pos }, r) + let v : t -> t' = fun (t, r) -> ({ result = t.type_of; pos = t.pos }, r) (** The variable has type string when starting with a '$' *) - let ident : - (S.pos, Get_type.t Lazy.t * t) S.variable -> Get_type.t Lazy.t -> t = - fun var _type_of -> + let ident : ctx:Qsp_syntax.S.extract_context -> (S.pos, t) S.variable -> t = + fun ~ctx var -> (* Extract the error from the index *) let report = match var.index with | None -> [] | Some (_, expr) -> - let _, r = expr in + let r = expr in r in - ({ pos = var.pos }, report) + let type_of = Option.get (ctx.f Get_type.expression_id) in + ({ pos = var.pos; type_of }, report) - let integer : S.pos -> string -> Get_type.t Lazy.t -> t = - fun pos value _type_of -> + let integer : ctx:Qsp_syntax.S.extract_context -> S.pos -> string -> t = + fun ~ctx pos value -> + let type_of = Option.get (ctx.f Get_type.expression_id) in let int_value = int_of_string_opt value in let report = @@ -183,42 +184,36 @@ module TypedExpression = struct | None -> Report.error pos "Invalid integer value" :: [] in - ({ pos }, report) + ({ pos; type_of }, report) let literal : - S.pos -> (Get_type.t Lazy.t * t) T.literal list -> Get_type.t Lazy.t -> t - = - fun pos values type_of -> - ignore type_of; + ctx:Qsp_syntax.S.extract_context -> S.pos -> t T.literal list -> t = + fun ~ctx pos values -> + let type_of = Option.get (ctx.f Get_type.expression_id) in let init = [] in let report = List.fold_left values ~init ~f:(fun report -> function | T.Text _ -> report - | T.Expression (_, t) -> + | T.Expression t -> let report = List.rev_append (snd t) report in report) in - ({ pos }, report) + ({ pos; type_of }, report) let function_ : - S.pos -> - T.function_ -> - (Get_type.t Lazy.t * t) list -> - Get_type.t Lazy.t -> - t = - fun pos function_ params _type_of -> + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.function_ -> t list -> t = + fun ~ctx pos function_ params -> + let type_of = Option.get (ctx.f Get_type.expression_id) in (* Accumulate the expressions and get the results, the report is given in the differents arguments, and we build a list with the type of the parameters. *) let types, report = - List.fold_left params ~init:([], []) - ~f:(fun (types, report) (type_of, param) -> - ignore type_of; + List.fold_left params ~init:([], []) ~f:(fun (types, report) param -> let t, r = param in - let arg = arg_of_repr type_of t.pos in + let arg = arg_of_repr t.type_of t.pos in (arg :: types, r @ report)) in - let types = List.rev types and default = { pos } in + let types = List.rev types and default = { pos; type_of } in match function_ with | Arrcomp | Arrpos | Arrsize | Countobj | Desc | Dyneval | Getobj | Instr @@ -231,7 +226,7 @@ module TypedExpression = struct let expected = Helper.[ Fixed Bool; Dynamic d; Dynamic d ] in let report = Helper.compare_args pos expected types report in (* Extract the type for the expression *) - ({ pos }, report) + ({ pos; type_of }, report) | Input | Input' -> (* Input should check the result if the variable is a num and raise a message in this case.*) @@ -259,7 +254,7 @@ module TypedExpression = struct (* All the arguments must have the same type *) let expected = Helper.[ Variable (Dynamic d) ] in let report = Helper.compare_args pos expected types report in - ({ pos }, report) + ({ pos; type_of }, report) | Mid | Mid' -> let expected = Helper.[ Fixed String; Variable (Fixed Integer) ] in let report = Helper.compare_args pos expected types report in @@ -294,29 +289,25 @@ module TypedExpression = struct (** Unary operator like [-123] or [+'Text']*) let uoperator : - S.pos -> T.uoperator -> Get_type.t Lazy.t * t -> Get_type.t Lazy.t -> t = - fun pos operator t1 type_of -> - ignore type_of; - let type_of, (t, report) = t1 in + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.uoperator -> t -> t = + fun ~ctx pos operator t1 -> + let t, report = t1 in match operator with | Add -> (t, report) | Neg | No -> - let types = [ arg_of_repr type_of t.pos ] in + let types = [ arg_of_repr t.type_of t.pos ] in let expected = Helper.[ Fixed Integer ] in let report = Helper.compare_args pos expected types report in - ({ pos }, report) + let type_of = Option.get (ctx.f Get_type.expression_id) in + ({ pos; type_of }, report) let boperator : - S.pos -> - T.boperator -> - Get_type.t Lazy.t * t -> - Get_type.t Lazy.t * t -> - Get_type.t Lazy.t -> - t = - fun pos operator (type_1, t1) (type_2, t2) type_of -> - ignore type_of; + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.boperator -> t -> t -> t = + fun ~ctx pos operator t1 t2 -> let t1, report1 = t1 in let t2, report2 = t2 in + let type_1 = t1.type_of and type_2 = t2.type_of in + let type_of = Option.get (ctx.f Get_type.expression_id) in let report = report1 @ report2 in @@ -329,7 +320,7 @@ module TypedExpression = struct When concatenating, it’s allowed to add an integer and a number. *) - ({ pos }, report) + ({ pos; type_of }, report) | T.Eq | T.Neq | Lt | Gte | Lte | Gt -> (* If the expression is '' or 0, we accept the comparaison as if instead of raising a warning *) @@ -347,26 +338,24 @@ module TypedExpression = struct report | report -> report in - ({ pos }, report) + ({ pos; type_of }, report) | T.Mod | T.Minus | T.Product | T.Div -> (* Operation over number *) let expected = Helper.[ Fixed Integer; Fixed Integer ] in let report = Helper.compare_args pos expected types report in - ({ pos }, report) + ({ pos; type_of }, report) | T.And | T.Or -> (* Operation over booleans *) let expected = Helper.[ Fixed Bool; Fixed Bool ] in let report = Helper.compare_args pos expected types report in - ({ pos }, report) + ({ pos; type_of }, report) end -module Expression = TypeBuilder.Make (TypedExpression) - module Instruction = struct type t = Report.t list type t' = Report.t list - let v : t -> t' = fun local_report -> local_report + let v : t -> t' = Fun.id type expression = Expression.t' @@ -446,17 +435,21 @@ module Instruction = struct let report = List.rev_append report' report in - match (op, Get_type.get_type (Lazy.force right_expression.result)) with + match (op, Get_type.Expression.get_type right_expression.result) with | T.Eq', Get_type.Integer -> (* Assigning an intger is allowed in a string variable, but raise a warning. *) - let var_type = Lazy.from_val (Get_type.ident variable) in + let var_type = + Get_type.Expression.ident ~ctx:{ f = (fun _ -> None) } variable + in let op1 = arg_of_repr var_type variable.pos in let expected = Helper.[ Fixed Integer ] in Helper.compare_args ~strict:true ~level:Report.Warn pos expected [ op1 ] report | _, _ -> ( - let var_type = Lazy.from_val (Get_type.ident variable) in + let var_type = + Get_type.Expression.ident ~ctx:{ f = (fun _ -> None) } variable + in let op1 = arg_of_repr var_type variable.pos in let op2 = arg_of_repr right_expression.result right_expression.pos in diff --git a/lib/checks/type_of.mli b/lib/checks/type_of.mli index de0f8f9..f2be559 100644 --- a/lib/checks/type_of.mli +++ b/lib/checks/type_of.mli @@ -1,7 +1,7 @@ -include Qsp_syntax.S.Analyzer +include Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t (** The module [type_of] populate the report with differents inconsistency - errors in the types. + errors in the types. - - Assigning a [string] value in an [integer] variable - - Comparing a [string] with an [integer] - - Giving the wrong type in the argument for a function and so one. *) + - Assigning a [string] value in an [integer] variable + - Comparing a [string] with an [integer] + - Giving the wrong type in the argument for a function and so one. *) diff --git a/lib/checks/write_only.ml b/lib/checks/write_only.ml index 8363703..2d78b59 100644 --- a/lib/checks/write_only.ml +++ b/lib/checks/write_only.ml @@ -15,17 +15,12 @@ let description = "Check variables never read" let active = ref false let is_global = true +let depends = [] -module Key = struct - type t = string +type ex = Qsp_syntax.Identifier.t - let equal = String.equal - let hash = Hashtbl.hash - let compare = String.compare -end - -module StringMap = Hashtbl.Make (Key) -module Set = Set.Make (Key) +module StringMap = Hashtbl.Make (String) +module Set = Set.Make (String) type data = { write : bool; read : bool; position : S.pos list } type context = (string * data) StringMap.t @@ -84,13 +79,16 @@ module Expression = struct let default _ map = ignore map end) - let ident : (S.pos, t) S.variable -> t = - fun variable filename map -> + let ident : ctx:Qsp_syntax.S.extract_context -> (S.pos, t) S.variable -> t = + fun ~ctx variable filename map -> + ignore ctx; (* Update the map and set the read flag *) set_readed variable.pos variable.name filename map - let literal : S.pos -> t T.literal list -> t = - fun pos l filename map -> + let literal : + ctx:Qsp_syntax.S.extract_context -> S.pos -> t T.literal list -> t = + fun ~ctx pos l filename map -> + ignore ctx; List.iter l ~f:(function | T.Text t -> set_readed pos ~update_only:true (String.uppercase_ascii t) filename @@ -99,13 +97,22 @@ module Expression = struct (* When the string contains an expression evaluate it *) exprs filename map) - let function_ : S.pos -> T.function_ -> t list -> t = - fun _ _ exprs filename map -> List.iter ~f:(fun v -> v filename map) exprs + let function_ : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.function_ -> t list -> t = + fun ~ctx _ _ exprs filename map -> + ignore ctx; + List.iter ~f:(fun v -> v filename map) exprs - let uoperator : S.pos -> T.uoperator -> t -> t = fun _ _ t map -> t map + let uoperator : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.uoperator -> t -> t = + fun ~ctx _ _ t map -> + ignore ctx; + t map - let boperator : S.pos -> T.boperator -> t -> t -> t = - fun _ _ t1 t2 filename map -> + let boperator : + ctx:Qsp_syntax.S.extract_context -> S.pos -> T.boperator -> t -> t -> t = + fun ~ctx _ _ t1 t2 filename map -> + ignore ctx; t1 filename map; t2 filename map end |