diff options
author | Chimrod <> | 2025-07-19 11:18:24 +0200 |
---|---|---|
committer | Chimrod <> | 2025-08-01 14:12:14 +0200 |
commit | 3046fb0d0c1ceac2c6a6ca9456e9e05671e0cef9 (patch) | |
tree | 8ba2700e541a6753499ceac54ced4f1d02a3b625 | |
parent | 406b7b79cd375b071f92ddee9cee14a98dc91281 (diff) |
39 files changed, 906 insertions, 663 deletions
diff --git a/bin/args.ml b/bin/args.ml index 1503d18..e0e1419 100644 --- a/bin/args.ml +++ b/bin/args.ml @@ -29,7 +29,9 @@ let disable_module modules identifier = String.sub identifier ~pos:1 ~len:(String.length identifier - 1) in List.iter modules ~f:(fun t -> - let (module C : Qsp_syntax.S.Analyzer) = Qsp_checks.Check.get_module t in + let (module C : Qsp_syntax.Analyzer.T) = + Qsp_syntax.Identifier.get_module t + in if String.equal C.identifier identifier then C.active := false) let enable_module modules identifier = @@ -37,7 +39,9 @@ let enable_module modules identifier = String.sub identifier ~pos:1 ~len:(String.length identifier - 1) in List.iter modules ~f:(fun t -> - let (module C : Qsp_syntax.S.Analyzer) = Qsp_checks.Check.get_module t in + let (module C : Qsp_syntax.Analyzer.T) = + Qsp_syntax.Identifier.get_module t + in if String.equal C.identifier identifier then C.active := true) let speclist printer = @@ -74,7 +78,7 @@ let speclist printer = common_arguments @ windows_arguments let parse : - modules:Qsp_syntax.Catalog.ex list -> + modules:Qsp_syntax.Identifier.t list -> list_tests:(Format.formatter -> unit) -> string list * t = fun ~modules ~list_tests -> diff --git a/bin/args.mli b/bin/args.mli index a98b258..151a4ca 100644 --- a/bin/args.mli +++ b/bin/args.mli @@ -4,6 +4,6 @@ type t = { reset_line : bool; filters : filters } (** All the arguments given from the command line *) val parse : - modules:Qsp_syntax.Catalog.ex list -> + modules:Qsp_syntax.Identifier.t list -> list_tests:(Format.formatter -> unit) -> string list * t diff --git a/bin/qsp_parser.ml b/bin/qsp_parser.ml index 65a4e4a..7ec3eff 100644 --- a/bin/qsp_parser.ml +++ b/bin/qsp_parser.ml @@ -27,17 +27,17 @@ let dynamic_context_id : Qsp_checks.Dynamics.context Type.Id.t = Type.Id.make () *) let available_checks = [ - Qsp_syntax.Catalog.build ~context_id:dynamic_context_id + Qsp_syntax.Identifier.build ~context_id:dynamic_context_id (module Qsp_checks.Dynamics); - Qsp_syntax.Catalog.build (module Qsp_checks.Type_of); - Qsp_syntax.Catalog.build (module Qsp_checks.Dead_end); - Qsp_syntax.Catalog.build (module Qsp_checks.Nested_strings); - Qsp_syntax.Catalog.build (module Qsp_checks.Locations); - Qsp_syntax.Catalog.build (module Qsp_checks.Dup_test); - Qsp_syntax.Catalog.build (module Qsp_checks.Write_only); + Qsp_syntax.Identifier.build (module Qsp_checks.Type_of); + Qsp_syntax.Identifier.build (module Qsp_checks.Dead_end); + Qsp_syntax.Identifier.build (module Qsp_checks.Nested_strings); + Qsp_syntax.Identifier.build (module Qsp_checks.Locations); + Qsp_syntax.Identifier.build (module Qsp_checks.Dup_test); + Qsp_syntax.Identifier.build (module Qsp_checks.Write_only); ] -let pp_module formatter (module A : Qsp_syntax.S.Analyzer) = +let pp_module formatter (module A : Qsp_syntax.Analyzer.T) = Format.fprintf formatter "%s" A.identifier; Format.pp_print_tab formatter (); (match !A.active with @@ -51,8 +51,8 @@ let pp_module formatter (module A : Qsp_syntax.S.Analyzer) = let pp_modules formatter = let max_length = List.fold_left available_checks ~init:0 ~f:(fun l v -> - let (module A : Qsp_syntax.S.Analyzer) = - Qsp_checks.Check.get_module v + let (module A : Qsp_syntax.Analyzer.T) = + Qsp_syntax.Identifier.get_module v in max l (String.length A.identifier)) in @@ -71,7 +71,7 @@ let pp_modules formatter = Format.fprintf formatter "%a" (Format.pp_print_list (fun f v -> - let m = Qsp_checks.Check.get_module v in + let m = Qsp_syntax.Identifier.get_module v in pp_module f m) ~pp_sep:(fun f () -> Format.pp_force_newline f ())) available_checks; @@ -83,15 +83,15 @@ let pp_modules formatter = The expression is declared lazy in order to be sure to apply the filters from the command line before. *) let checkers : - (module Qsp_syntax.S.Analyzer + (module Qsp_syntax.Analyzer.T with type context = Qsp_checks.Check.result array) Lazy.t = lazy (let module Check = Qsp_checks.Check.Make (struct let t = List.filter available_checks ~f:(fun v -> - let (module A : Qsp_syntax.S.Analyzer) = - Qsp_checks.Check.get_module v + let (module A : Qsp_syntax.Analyzer.T) = + Qsp_syntax.Identifier.get_module v in !A.active) |> Array.of_list @@ -144,7 +144,7 @@ let display_result : The function update the context (list of errors) passed in arguments. *) let parse_location : ctx:ctx ref -> - (module Qsp_syntax.S.Analyzer + (module Qsp_syntax.Analyzer.T with type context = Qsp_checks.Check.result array) -> Qsp_checks.Check.result array -> Qparser.Lexbuf.t -> @@ -210,8 +210,8 @@ let () = | ".qsrc" -> (* Deactivate the tests which only applies to a global file *) List.iter available_checks ~f:(fun t -> - let (module C : Qsp_syntax.S.Analyzer) = - Qsp_checks.Check.get_module t + let (module C : Qsp_syntax.Analyzer.T) = + Qsp_syntax.Identifier.get_module t in if C.is_global && !C.active then C.active := false); diff --git a/dune-project b/dune-project index d89e83b..4646023 100644 --- a/dune-project +++ b/dune-project @@ -29,6 +29,7 @@ sedlex fmt ppx_deriving + tsort ) (tags (topics "to describe" your project))) 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 ebed0df..34d953f 100644 --- a/lib/checks/check.mli +++ b/lib/checks/check.mli @@ -13,19 +13,19 @@ 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 + Qsp_syntax.Analyzer.T with type Location.t = result array and type context = result array end diff --git a/lib/checks/compose.ml b/lib/checks/compose.ml deleted file mode 100644 index b29c22e..0000000 --- a/lib/checks/compose.ml +++ /dev/null @@ -1,130 +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 - -module TypeBuilder = Expression (Get_type) -(** Builder adding the type for the expression *) 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 0c4d761..0ec1084 100644 --- a/lib/checks/default.ml +++ b/lib/checks/default.ml @@ -21,25 +21,56 @@ struct 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 diff --git a/lib/checks/dune b/lib/checks/dune index 3bd22e0..75b311b 100644 --- a/lib/checks/dune +++ b/lib/checks/dune @@ -1,6 +1,7 @@ (library (name qsp_checks) (libraries + tsort qsp_syntax ) diff --git a/lib/checks/dup_test.ml b/lib/checks/dup_test.ml index c29eca9..4de9a4d 100644 --- a/lib/checks/dup_test.ml +++ b/lib/checks/dup_test.ml @@ -13,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 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 index 0c16ff8..f88550b 100644 --- a/lib/checks/dynamics.ml +++ b/lib/checks/dynamics.ml @@ -7,7 +7,9 @@ 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 @@ -54,8 +56,10 @@ module Expression = struct let v : t -> t' = Fun.id (** Only keep the raw strings *) - let literal : S.pos -> t T.literal list -> t = - fun position content -> + 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 } @@ -91,13 +95,16 @@ module Expression = struct (** 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 : S.pos -> string -> t = - fun position content -> Text { content; position } + 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 : (S.pos, t) S.variable -> t = - fun ({ index; _ } as ident) -> + 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) diff --git a/lib/checks/dynamics.mli b/lib/checks/dynamics.mli index b4cdc96..588a05e 100644 --- a/lib/checks/dynamics.mli +++ b/lib/checks/dynamics.mli @@ -1,4 +1,4 @@ -include Qsp_syntax.S.Analyzer +include Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t type text = { content : string; position : Qsp_syntax.S.pos } diff --git a/lib/checks/get_type.ml b/lib/checks/get_type.ml index 2486afa..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 -> 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 + 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 8e5f500..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; @@ -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 diff --git a/lib/checks/nested_strings.ml b/lib/checks/nested_strings.ml index 51c5258..d4a7947 100644 --- a/lib/checks/nested_strings.ml +++ b/lib/checks/nested_strings.ml @@ -7,80 +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 Expression = Compose.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. 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 42f9a2d..243c8b3 100644 --- a/lib/checks/type_of.ml +++ b/lib/checks/type_of.ml @@ -12,12 +12,15 @@ 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 @@ -143,35 +146,35 @@ module Helper = struct msg :: report end -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 = @@ -181,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 @@ -229,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.*) @@ -257,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 @@ -292,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 @@ -327,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 *) @@ -345,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 = Compose.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' @@ -444,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 e2c3d7e..2d78b59 100644 --- a/lib/checks/write_only.ml +++ b/lib/checks/write_only.ml @@ -15,6 +15,9 @@ let description = "Check variables never read" let active = ref false let is_global = true +let depends = [] + +type ex = Qsp_syntax.Identifier.t module StringMap = Hashtbl.Make (String) module Set = Set.Make (String) @@ -76,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 @@ -91,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 diff --git a/lib/qparser/analyzer.ml b/lib/qparser/analyzer.ml index b4eeba0..fc0ed6d 100644 --- a/lib/qparser/analyzer.ml +++ b/lib/qparser/analyzer.ml @@ -14,14 +14,14 @@ let get_lexer : See [syntax/S] *) let rec parse : type a context. - (module Qsp_syntax.S.Analyzer + (module Qsp_syntax.Analyzer.T with type Location.t = a and type context = context) -> lexer -> Lexbuf.t -> context -> (a result, Qsp_syntax.Report.t) Result.t = - fun (module S : Qsp_syntax.S.Analyzer + fun (module S : Qsp_syntax.Analyzer.T with type Location.t = a and type context = context) -> let module Parser = Parser.Make (S) in diff --git a/lib/qparser/analyzer.mli b/lib/qparser/analyzer.mli index 817be6c..6e2f752 100644 --- a/lib/qparser/analyzer.mli +++ b/lib/qparser/analyzer.mli @@ -2,7 +2,7 @@ type 'a result = { content : 'a; report : Qsp_syntax.Report.t list } type lexer = Location | Dynamic val parse : - (module Qsp_syntax.S.Analyzer + (module Qsp_syntax.Analyzer.T with type Location.t = 'a and type context = 'context) -> lexer -> diff --git a/lib/qparser/parser.mly b/lib/qparser/parser.mly index 2fadccf..1caf962 100644 --- a/lib/qparser/parser.mly +++ b/lib/qparser/parser.mly @@ -17,7 +17,7 @@ module Helper = Qsp_syntax.S.Helper(Analyzer.Expression) %} -%parameter<Analyzer: Qsp_syntax.S.Analyzer> +%parameter<Analyzer: Qsp_syntax.Analyzer.T> %start <(Analyzer.context -> Analyzer.Location.t)>main %start<(Analyzer.context -> Analyzer.Location.t)>dynamics diff --git a/lib/qparser/qsp_expression.mly b/lib/qparser/qsp_expression.mly index c6b7564..f037051 100644 --- a/lib/qparser/qsp_expression.mly +++ b/lib/qparser/qsp_expression.mly @@ -27,26 +27,26 @@ { ex } | op = unary_operator expr = expression - { Analyzer.Expression.uoperator $loc op expr } + { Analyzer.Expression.uoperator ~ctx:{f=(fun _ -> None)} $loc op expr } %prec NO | expr1 = expression op = binary_operator expr2 = expression - { Analyzer.Expression.boperator $loc op expr1 expr2 } + { Analyzer.Expression.boperator ~ctx:{f=(fun _ -> None)} $loc op expr1 expr2 } | v = delimited(TEXT_MARKER, literal*, TEXT_MARKER) - { Analyzer.Expression.literal $loc v } - | i = INTEGER { Analyzer.Expression.integer $loc i } - | v = variable { Analyzer.Expression.ident v } + { Analyzer.Expression.literal ~ctx:{f=(fun _ -> None)} $loc v } + | i = INTEGER { Analyzer.Expression.integer ~ctx:{f=(fun _ -> None)} $loc i } + | v = variable { Analyzer.Expression.ident ~ctx:{f=(fun _ -> None)} v } %prec p_variable | k = FUNCTION arg = argument(expression) { - (Analyzer.Expression.function_ $loc k arg) + (Analyzer.Expression.function_ ~ctx:{f=(fun _ -> None)} $loc k arg) } | k = FUNCTION_NOARGS { - (Analyzer.Expression.function_ $loc k []) + (Analyzer.Expression.function_ ~ctx:{f=(fun _ -> None)} $loc k []) } literal: | v = LITERAL { Qsp_syntax.T.Text v } diff --git a/lib/syntax/S.ml b/lib/syntax/S.ml index a3c74ca..04490af 100644 --- a/lib/syntax/S.ml +++ b/lib/syntax/S.ml @@ -29,6 +29,9 @@ type ('a, 'b) variable = { pos : 'a; name : string; index : 'b option } type ('a, 'b) clause = pos * 'a * 'b list +type extract_context = { f : 'a. 'a Type.Id.t -> 'a option } [@@unboxed] +(** Extract the given value from the context *) + (** {1 Checker Signature} *) (** Represent the evaluation over an expression *) @@ -40,22 +43,22 @@ module type Expression = sig (** External type used outside of the module *) val v : t -> t' - val ident : (pos, t) variable -> t + val ident : ctx:extract_context -> (pos, t) variable -> t (* Basic values, text, number… *) - val integer : pos -> string -> t - val literal : pos -> t T.literal list -> t + val integer : ctx:extract_context -> pos -> string -> t + val literal : ctx:extract_context -> pos -> t T.literal list -> t - val function_ : pos -> T.function_ -> t list -> t + val function_ : ctx:extract_context -> pos -> T.function_ -> t list -> t (** Call a function. The functions list is hardcoded in lib/lexer.mll *) - val uoperator : pos -> T.uoperator -> t -> t + val uoperator : ctx:extract_context -> pos -> T.uoperator -> t -> t (** Unary operator like [-123] or [+'Text']*) - val boperator : pos -> T.boperator -> t -> t -> t + val boperator : ctx:extract_context -> pos -> T.boperator -> t -> t -> t (** Binary operator, for a comparaison, or an operation *) end @@ -108,41 +111,6 @@ module type Location = sig val location : context -> pos -> instruction list -> t end -(** {1 Unified module used by the parser} *) - -module type Analyzer = sig - val identifier : string - (** Identifier for the module *) - - val description : string - (** Short description*) - - val active : bool ref - (** Is the test active or not *) - - val is_global : bool - (** Declare the checker as global. It requires to run over the whole file and - will be disabled if the application only check a single location. - - Also, the test will be disabled if a syntax error is reported during the - parsing, because this tell that I haven’t been able to analyse the whole - source code. *) - - type context - (** Context used to keep information during the whole test *) - - val initialize : unit -> context - (** Initialize the context before starting to parse the content *) - - module Expression : Expression - module Instruction : Instruction with type expression := Expression.t' - - module Location : - Location with type instruction := Instruction.t' and type context := context - - val finalize : context -> (string * Report.t) list -end - (** Helper module used in order to convert elements from the differents representation levels. diff --git a/lib/syntax/analyzer.ml b/lib/syntax/analyzer.ml new file mode 100644 index 0000000..22c1696 --- /dev/null +++ b/lib/syntax/analyzer.ml @@ -0,0 +1,43 @@ +module type T = sig + type ex + (** The type is not given, but we do not have much choice. Because of + recursive definition, the type is left blank here, but constraint will be + defined later, and this type shall be a [ex] *) + + val depends : ex list + (** Dependencies are module required to be executed before. The result for + them can be accessed with the ctx argument given in the functions *) + + val identifier : string + (** Identifier for the module *) + + val description : string + (** Short description*) + + val active : bool ref + (** Is the test active or not *) + + val is_global : bool + (** Declare the checker as global. It requires to run over the whole file and + will be disabled if the application only check a single location. + + Also, the test will be disabled if a syntax error is reported during the + parsing, because this tell that I haven’t been able to analyse the whole + source code. *) + + type context + (** Context used to keep information during the whole test *) + + val initialize : unit -> context + (** Initialize the context before starting to parse the content *) + + module Expression : S.Expression + module Instruction : S.Instruction with type expression := Expression.t' + + module Location : + S.Location + with type instruction := Instruction.t' + and type context := context + + val finalize : context -> (string * Report.t) list +end diff --git a/lib/syntax/dune b/lib/syntax/dune index 9832809..4bc26be 100644 --- a/lib/syntax/dune +++ b/lib/syntax/dune @@ -1,8 +1,4 @@ (library (name qsp_syntax) - - (preprocess (pps - ppx_deriving.show - ppx_deriving.enum - ppx_deriving.ord - ppx_deriving.eq ))) + (preprocess + (pps ppx_deriving.show ppx_deriving.enum ppx_deriving.ord ppx_deriving.eq))) diff --git a/lib/syntax/catalog.ml b/lib/syntax/identifier.ml index 5ad0bbd..422171c 100644 --- a/lib/syntax/catalog.ml +++ b/lib/syntax/identifier.ml @@ -1,13 +1,14 @@ -type ex = +type t = | E : { module_ : - (module S.Analyzer + (module Analyzer.T with type Expression.t = 'a and type Expression.t' = 'b and type Instruction.t = 'c and type Instruction.t' = 'd and type Location.t = 'e - and type context = 'f); + and type context = 'f + and type ex = t); expr_witness : 'a Type.Id.t; expr' : 'b Type.Id.t; instr_witness : 'c Type.Id.t; @@ -15,21 +16,27 @@ type ex = location_witness : 'e Type.Id.t; context : 'f Type.Id.t; } - -> ex (** Type of check to apply *) + -> t (** Type of check to apply *) + +let get_module : t -> (module Analyzer.T) = + fun (E { module_; _ }) -> (module_ :> (module Analyzer.T)) let build : - ?location_id:'a Type.Id.t -> - ?context_id:'b Type.Id.t -> - (module S.Analyzer - with type Expression.t = _ + ?expression_id:'a Type.Id.t -> + ?location_id:'b Type.Id.t -> + ?context_id:'c Type.Id.t -> + (module Analyzer.T + with type Expression.t = 'a and type Expression.t' = _ and type Instruction.t = _ and type Instruction.t' = _ - and type Location.t = 'a - and type context = 'b) -> - ex = - fun ?location_id ?context_id module_ -> - let expr_witness = Type.Id.make () + and type Location.t = 'b + and type context = 'c + and type ex = t) -> + t = + fun ?expression_id ?location_id ?context_id module_ -> + let expr_witness = + match expression_id with None -> Type.Id.make () | Some v -> v and expr' = Type.Id.make () and instr_witness = Type.Id.make () and instr' = Type.Id.make () diff --git a/lib/syntax/catalog.mli b/lib/syntax/identifier.mli index a386d4a..4c6387b 100644 --- a/lib/syntax/catalog.mli +++ b/lib/syntax/identifier.mli @@ -1,13 +1,14 @@ -type ex = +type t = | E : { module_ : - (module S.Analyzer + (module Analyzer.T with type Expression.t = 'a and type Expression.t' = 'b and type Instruction.t = 'c and type Instruction.t' = 'd and type Location.t = 'e - and type context = 'f); + and type context = 'f + and type ex = t); expr_witness : 'a Type.Id.t; expr' : 'b Type.Id.t; instr_witness : 'c Type.Id.t; @@ -15,18 +16,22 @@ type ex = location_witness : 'e Type.Id.t; context : 'f Type.Id.t; } - -> ex (** Type of check to apply *) + -> t (** Type of check to apply *) val build : - ?location_id:'a Type.Id.t -> - ?context_id:'b Type.Id.t -> - (module S.Analyzer - with type Expression.t = _ + ?expression_id:'a Type.Id.t -> + ?location_id:'b Type.Id.t -> + ?context_id:'c Type.Id.t -> + (module Analyzer.T + with type Expression.t = 'a and type Expression.t' = _ and type Instruction.t = _ and type Instruction.t' = _ - and type Location.t = 'a - and type context = 'b) -> - ex + and type Location.t = 'b + and type context = 'c + and type ex = t) -> + t (** Build a new check from a module following S.Analyzer signature. ypeid Return the result type which hold the final result value, and checker itself. *) + +val get_module : t -> (module Analyzer.T) diff --git a/lib/syntax/tree.ml b/lib/syntax/tree.ml index 0074df8..c3edcdc 100644 --- a/lib/syntax/tree.ml +++ b/lib/syntax/tree.ml @@ -7,9 +7,12 @@ let active = ref true type context = unit +let depends = [] let initialize = Fun.id let finalize () = [] +type ex = Identifier.t + module Ast = struct type 'a literal = 'a T.literal = Text of string | Expression of 'a [@@deriving eq, show] @@ -88,24 +91,36 @@ end = struct Hashtbl.hash (f pos, name, List.map ~f:(hash f) args) let v : t -> t' = fun t -> t - let integer : S.pos -> string -> t = fun pos i -> Ast.Integer (pos, i) - - let literal : S.pos -> t T.literal list -> t = - fun pos l -> Ast.Literal (pos, l) - - let function_ : S.pos -> T.function_ -> t list -> t = - fun pos name args -> Ast.Function (pos, name, args) - - let uoperator : S.pos -> T.uoperator -> t -> t = - fun pos op expression -> Ast.Op (pos, op, expression) - let boperator : S.pos -> T.boperator -> t -> t -> t = - fun pos op op1 op2 -> + let integer : ctx:S.extract_context -> S.pos -> string -> t = + fun ~ctx pos i -> + ignore ctx; + Ast.Integer (pos, i) + + let literal : ctx:S.extract_context -> S.pos -> t T.literal list -> t = + fun ~ctx pos l -> + ignore ctx; + Ast.Literal (pos, l) + + let function_ : ctx:S.extract_context -> S.pos -> T.function_ -> t list -> t = + fun ~ctx pos name args -> + ignore ctx; + Ast.Function (pos, name, args) + + let uoperator : ctx:S.extract_context -> S.pos -> T.uoperator -> t -> t = + fun ~ctx pos op expression -> + ignore ctx; + Ast.Op (pos, op, expression) + + let boperator : ctx:S.extract_context -> S.pos -> T.boperator -> t -> t -> t = + fun ~ctx pos op op1 op2 -> + ignore ctx; let op1 = op1 and op2 = op2 in Ast.BinaryOp (pos, op, op1, op2) - let ident : (S.pos, t) S.variable -> t = - fun { pos; name; index } -> + let ident : ctx:S.extract_context -> (S.pos, t) S.variable -> t = + fun ~ctx { pos; name; index } -> + ignore ctx; let index = Option.map (fun i -> i) index in Ast.Ident { pos; name; index } end diff --git a/lib/syntax/tree.mli b/lib/syntax/tree.mli index 9ed442b..097a7ac 100644 --- a/lib/syntax/tree.mli +++ b/lib/syntax/tree.mli @@ -1,9 +1,7 @@ -(** - Implementation for S.Analyzer for building a complete Ast. +(** Implementation for S.Analyzer for building a complete Ast. Used in the unit test in order to check if the grammar is interpreted as - expected, not really usefull over a big qsp. - *) + expected, not really usefull over a big qsp. *) (** This module is the result of the evaluation. *) module Ast : sig @@ -53,8 +51,9 @@ module Expression : sig end include - S.Analyzer + Analyzer.T with module Expression := Expression and type Instruction.t' = S.pos Ast.statement and type Location.t = S.pos * S.pos Ast.statement list and type context = unit + and type ex = Identifier.t diff --git a/test/get_type.ml b/test/get_type.ml index 55f087e..56b4689 100644 --- a/test/get_type.ml +++ b/test/get_type.ml @@ -3,79 +3,84 @@ module T = Qsp_syntax.T let _position = (Lexing.dummy_pos, Lexing.dummy_pos) -let type_of : Get_type.t Alcotest.testable = - Alcotest.testable Get_type.pp Get_type.equal +let type_of : Get_type.Expression.t Alcotest.testable = + Alcotest.testable Get_type.Expression.pp Get_type.Expression.equal + +let ctx = Qsp_syntax.S.{ f = (fun _ -> None) } let add_number () = let actual = - Get_type.boperator _position T.Plus - (Get_type.integer _position "0") - (Get_type.integer _position "1") + Get_type.Expression.boperator ~ctx _position T.Plus + (Get_type.Expression.integer ~ctx _position "0") + (Get_type.Expression.integer ~ctx _position "1") in - let expected = Get_type.(Raw Integer) in + let expected = Get_type.Expression.(Raw Integer) in let msg = "Adding integer" in Alcotest.(check' type_of ~msg ~expected ~actual) let add_literal_number () = let actual = - Get_type.boperator _position T.Plus - (Get_type.literal _position [ T.Text "2" ]) - (Get_type.integer _position "1") + Get_type.Expression.boperator ~ctx _position T.Plus + (Get_type.Expression.literal ~ctx _position [ T.Text "2" ]) + (Get_type.Expression.integer ~ctx _position "1") in - let expected = Get_type.(Raw Integer) in + let expected = Get_type.Expression.(Raw Integer) in let msg = "A string containing integer is considered as integer" in Alcotest.(check' type_of ~msg ~expected ~actual) let concat_text () = let actual = - Get_type.boperator _position T.Plus - (Get_type.literal _position [ T.Text "a" ]) - (Get_type.integer _position "1") + Get_type.Expression.boperator ~ctx _position T.Plus + (Get_type.Expression.literal ~ctx _position [ T.Text "a" ]) + (Get_type.Expression.integer ~ctx _position "1") in - let expected = Get_type.(Raw String) in + let expected = Get_type.Expression.(Raw String) in let msg = "Concatenate" in Alcotest.(check' type_of ~msg ~expected ~actual) let literal_1 () = let actual = - Get_type.literal _position [ T.Expression (Get_type.Raw Integer) ] - and expected = Get_type.(Raw NumericString) in + Get_type.Expression.literal ~ctx _position + [ T.Expression (Get_type.Expression.Raw Integer) ] + and expected = Get_type.Expression.(Raw NumericString) in let msg = "" in Alcotest.(check' type_of ~msg ~expected ~actual) let literal_2 () = let actual = - Get_type.literal _position - Get_type.[ T.Text "1"; T.Expression (Raw Integer) ] - and expected = Get_type.(Raw NumericString) in + Get_type.Expression.literal ~ctx _position + Get_type.Expression.[ T.Text "1"; T.Expression (Raw Integer) ] + and expected = Get_type.Expression.(Raw NumericString) in let msg = "" in Alcotest.(check' type_of ~msg ~expected ~actual) let literal_3 () = let actual = - Get_type.literal _position - Get_type.[ T.Text "b"; T.Expression (Raw Integer) ] - and expected = Get_type.(Raw String) in + Get_type.Expression.literal ~ctx _position + Get_type.Expression.[ T.Text "b"; T.Expression (Raw Integer) ] + and expected = Get_type.Expression.(Raw String) in let msg = "" in Alcotest.(check' type_of ~msg ~expected ~actual) let literal_4 () = let actual = - Get_type.literal _position [ T.Expression (Get_type.Variable Integer) ] - and expected = Get_type.(Variable NumericString) in + Get_type.Expression.literal ~ctx _position + Get_type.Expression.[ T.Expression (Variable Integer) ] + and expected = Get_type.Expression.(Variable NumericString) in let msg = "" in Alcotest.(check' type_of ~msg ~expected ~actual) let min () = - let actual = Get_type.function_ _position T.Min [] in - let expected = Get_type.(Raw Bool) in + let actual = Get_type.Expression.function_ ~ctx _position T.Min [] in + let expected = Get_type.Expression.(Raw Bool) in let msg = "The function min without argument return a default value" in Alcotest.(check' type_of ~msg ~expected ~actual); let actual = - Get_type.function_ _position T.Min [ Get_type.literal _position [] ] + Get_type.Expression.function_ ~ctx _position T.Min + [ Get_type.Expression.literal ~ctx _position [] ] in - let expected = Get_type.(Variable NumericString) in + let expected = Get_type.Expression.(Variable NumericString) in let msg = "The function min with a literal will take the literal as the name of an \ array" @@ -83,10 +88,11 @@ let min () = Alcotest.(check' type_of ~msg ~expected ~actual); let actual = - Get_type.function_ _position T.Min - [ Get_type.integer _position ""; Get_type.integer _position "" ] + Get_type.Expression.function_ ~ctx _position T.Min + Get_type.Expression. + [ integer ~ctx _position ""; integer ~ctx _position "" ] in - let expected = Get_type.(Raw Integer) in + let expected = Get_type.Expression.(Raw Integer) in let msg = "With two or more arguments, return the type of the first one" in Alcotest.(check' type_of ~msg ~expected ~actual) diff --git a/test/location.ml b/test/location.ml index cf2008f..decf270 100644 --- a/test/location.ml +++ b/test/location.ml @@ -5,7 +5,7 @@ let _position = (Lexing.dummy_pos, Lexing.dummy_pos) let error_message = [ ( "Location", - Check. + Make_checkTest. { level = Error; loc = _position; @@ -27,7 +27,7 @@ let if_missing_gs () = if 0: gs 'unknown_place'|} error_message let test = - ( "Locations", + ( __FILE__, [ Alcotest.test_case "Ok" `Quick ok; Alcotest.test_case "Ok upper" `Quick ok_upper; diff --git a/test/make_checkTest.ml b/test/make_checkTest.ml index a863214..7ffd17c 100644 --- a/test/make_checkTest.ml +++ b/test/make_checkTest.ml @@ -1,30 +1,38 @@ -(** Build a parser for a specific check module *) -module M (Check : Qsp_syntax.S.Analyzer) = struct - module S = Qsp_syntax.S +module S = Qsp_syntax.S + +type pos = S.pos - let pp_pos = Qsp_syntax.Report.pp_pos +let pp_pos = Qsp_syntax.Report.pp_pos +let equal_pos : pos -> pos -> bool = fun _ _ -> true - type pos = S.pos +type t = Qsp_syntax.Report.t = { + level : Qsp_syntax.Report.level; + loc : pos; + message : string; +} +[@@deriving show, eq] - let equal_pos : pos -> pos -> bool = fun _ _ -> true +let report : t list Alcotest.testable = + Alcotest.list @@ Alcotest.testable Qsp_syntax.Report.pp equal - type t = Qsp_syntax.Report.t = { - level : Qsp_syntax.Report.level; - loc : pos; - message : string; - } - [@@deriving show, eq] +let report_global : (string * t) list Alcotest.testable = + Alcotest.list + @@ Alcotest.pair Alcotest.string + (Alcotest.testable Qsp_syntax.Report.pp equal) - let report : t list Alcotest.testable = - Alcotest.list @@ Alcotest.testable Qsp_syntax.Report.pp equal +(** Build a parser for a specific check module *) +module M + (Checkable : Qsp_syntax.Analyzer.T with type ex = Qsp_syntax.Identifier.t) = +struct + let context_id = Type.Id.make () - let report_global : (string * t) list Alcotest.testable = - Alcotest.list - @@ Alcotest.pair Alcotest.string - (Alcotest.testable Qsp_syntax.Report.pp equal) + (* Build the test module with a single test inside. *) + module Check = Qsp_checks.Check.Make (struct + let t = [| Qsp_syntax.Identifier.build ~context_id (module Checkable) |] + end) let _parse : - ?context:Check.context -> + ?context:Checkable.context -> Qparser.Analyzer.lexer -> string -> (Check.Location.t Qparser.Analyzer.result, t) result = @@ -32,7 +40,22 @@ module M (Check : Qsp_syntax.S.Analyzer) = struct let lexing = Sedlexing.Latin1.from_string content |> Qparser.Lexbuf.from_lexbuf in - let context = Option.value context ~default:(Check.initialize ()) in + (* Initialize the context *inside* the Check module. This works by + editing the context we created. + + We have the context id (created at the begining of the module), which is + required to get the value. *) + let context = + match context with + | None -> Check.initialize () + | Some c -> ( + let init = Check.initialize () in + match Qsp_checks.Check.set context_id init.(0) c with + | None -> raise Not_found + | Some v -> + init.(0) <- v; + init) + in Qparser.Analyzer.parse (module Check) lexer lexing context let get_report : @@ -55,7 +78,7 @@ module M (Check : Qsp_syntax.S.Analyzer) = struct let _location = Printf.sprintf {|# Location %s ------- |} literal in - let context = Check.initialize () in + let context = Checkable.initialize () in let actual = get_report @@ _parse ~context Qparser.Analyzer.Location _location in @@ -64,6 +87,6 @@ module M (Check : Qsp_syntax.S.Analyzer) = struct check' report ~msg:"Error reported during parsing" ~expected:[] ~actual) in let msg = literal in - let actual = Check.finalize context in + let actual = Checkable.finalize context in Alcotest.(check' report_global ~msg ~expected ~actual) end diff --git a/test/syntax.ml b/test/syntax.ml index ff5a3ca..ce3e89e 100644 --- a/test/syntax.ml +++ b/test/syntax.ml @@ -5,7 +5,7 @@ module S = Qsp_syntax.S module T = Qsp_syntax.T let location_id = Type.Id.make () -let e1 = Qsp_syntax.Catalog.build ~location_id (module Tree) +let e1 = Qsp_syntax.Identifier.build ~location_id (module Tree) module Parser = Check.Make (struct let t = [| e1 |] diff --git a/test/type_of.ml b/test/type_of.ml index e816bc7..1b84faa 100644 --- a/test/type_of.ml +++ b/test/type_of.ml @@ -78,7 +78,7 @@ let concat_text () = _test_instruction {|$a = 'A' + 1|} [] let increment_string () = _test_instruction {|$a += 1|} (message' Error) let test = - ( "Typechecking", + ( __FILE__, [ Alcotest.test_case "Assign str to int" `Quick type_mismatch; Alcotest.test_case "$str = int" `Quick assign_int_str; |