qsp_parser.ml 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. open StdLabels
  2. module Report = Qsp_syntax.Report
  3. (** Filter the results given by the analysis *)
  4. let filter_report : Args.filters -> Report.t list -> Report.t -> Report.t list =
  5. fun filters reports r ->
  6. let is_ok =
  7. match filters.level with
  8. | None -> true
  9. | Some level -> Report.level_to_enum level >= Report.level_to_enum r.level
  10. in
  11. match is_ok with true -> r :: reports | _ -> reports
  12. type ctx = { error_nb : int; warn_nb : int; debug_nb : int; fatal_error : bool }
  13. (*
  14. List all the controls to apply
  15. *)
  16. let available_checks =
  17. [
  18. snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Type_of);
  19. snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Dead_end);
  20. snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Nested_strings);
  21. snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Locations);
  22. snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Dup_test);
  23. snd @@ Qsp_syntax.Check.build (module Qsp_syntax.Write_only);
  24. ]
  25. let pp_module formatter (module A : Qsp_syntax.S.Analyzer) =
  26. Format.fprintf formatter "%s" A.identifier;
  27. Format.pp_print_tab formatter ();
  28. (match !A.active with
  29. | true -> Format.fprintf formatter "*"
  30. | false -> Format.fprintf formatter " ");
  31. Format.pp_print_tab formatter ();
  32. Format.fprintf formatter "%s" A.description;
  33. ()
  34. (** Print all the available modules *)
  35. let pp_modules formatter =
  36. let max_length =
  37. List.fold_left available_checks ~init:0 ~f:(fun l v ->
  38. let (module A : Qsp_syntax.S.Analyzer) =
  39. Qsp_syntax.Check.get_module v
  40. in
  41. max l (String.length A.identifier))
  42. in
  43. Format.pp_open_tbox formatter ();
  44. (* Print the name, left justified, with enought spaces for the all the
  45. identifiers *)
  46. Format.fprintf formatter "%-*s" (succ max_length) "Name";
  47. (* Tab delimiter *)
  48. Format.pp_set_tab formatter ();
  49. Format.fprintf formatter "Active ";
  50. Format.pp_set_tab formatter ();
  51. Format.fprintf formatter "Description@\n";
  52. Format.fprintf formatter "%a"
  53. (Format.pp_print_list
  54. (fun f v ->
  55. let m = Qsp_syntax.Check.get_module v in
  56. pp_module f m)
  57. ~pp_sep:(fun f () -> Format.pp_force_newline f ()))
  58. available_checks;
  59. Format.pp_close_tbox formatter ();
  60. Format.pp_print_break formatter 0 0
  61. (** Get all the tests to apply.
  62. The expression is declared lazy in order to be sure to apply the filters
  63. from the command line before. *)
  64. let checkers : (module Qsp_syntax.S.Analyzer) Lazy.t =
  65. lazy
  66. (let module Check = Qsp_syntax.Check.Make (struct
  67. let t =
  68. List.filter available_checks ~f:(fun v ->
  69. let (module A : Qsp_syntax.S.Analyzer) =
  70. Qsp_syntax.Check.get_module v
  71. in
  72. !A.active)
  73. |> Array.of_list
  74. end) in
  75. (module Check))
  76. (** Read the source file until getting a report (the whole location has been
  77. read properly), or until the first syntax error.
  78. The function update the context (list of errors) passed in arguments. *)
  79. let parse_location :
  80. type context.
  81. ctx:ctx ref ->
  82. (module Qsp_syntax.S.Analyzer with type context = context) ->
  83. context ->
  84. Qparser.Lexbuf.t ->
  85. Args.filters ->
  86. unit =
  87. fun ~ctx (module Check) context lexbuf filters ->
  88. let result =
  89. Qparser.Analyzer.parse (module Check) lexbuf context
  90. |> Result.map (fun f ->
  91. List.fold_left f.Qparser.Analyzer.report ~init:[]
  92. ~f:(filter_report filters)
  93. |> List.sort ~cmp:Report.compare)
  94. in
  95. match result with
  96. | Ok [] -> ()
  97. | Ok report ->
  98. (* Display the result *)
  99. let start_position, _ = Qparser.Lexbuf.positions lexbuf in
  100. Format.fprintf Format.std_formatter "Location@ %s@;@[%a@]@."
  101. start_position.Lexing.pos_fname Report.pp_result report;
  102. List.iter report ~f:(fun report ->
  103. match report.Report.level with
  104. | Error -> ctx := { !ctx with error_nb = succ !ctx.error_nb }
  105. | Warn -> ctx := { !ctx with warn_nb = succ !ctx.warn_nb }
  106. | Debug -> ctx := { !ctx with debug_nb = succ !ctx.debug_nb })
  107. | Error e ->
  108. (* Syntax error, we haven’t been able to run the test *)
  109. let start_position, _ = Qparser.Lexbuf.positions lexbuf in
  110. Format.fprintf Format.std_formatter "Location@ %s@;@[%a]@."
  111. start_position.Lexing.pos_fname Report.pp e;
  112. ctx := { !ctx with error_nb = succ !ctx.error_nb; fatal_error = true }
  113. let () =
  114. let file_names, parameters =
  115. Args.parse ~modules:available_checks ~list_tests:pp_modules
  116. in
  117. let file_name =
  118. List.filter ~f:(fun name -> name.[0] != '+') file_names |> List.hd
  119. in
  120. let ic = Stdlib.open_in_bin file_name in
  121. (*let lexer = Lexing.from_channel ~with_positions:true ic in*)
  122. let lexer, parameters =
  123. match Filename.extension file_name with
  124. | ".qsrc" ->
  125. (* Deactivate the tests which only applies to a global file *)
  126. List.iter available_checks ~f:(fun t ->
  127. let (module C : Qsp_syntax.S.Analyzer) =
  128. Qsp_syntax.Check.get_module t
  129. in
  130. if C.is_global && !C.active then C.active := false);
  131. (* The source file are in UTF-8, and we can use the file line number as
  132. we have only a single location. *)
  133. ( Sedlexing.Utf8.from_channel ic,
  134. { parameters with reset_line = parameters.Args.reset_line || false }
  135. )
  136. | ".txt" ->
  137. (Sedlexing.Utf16.from_channel ic (Some Little_endian), parameters)
  138. | _ -> raise (Failure "unknown extension")
  139. in
  140. let lexer =
  141. Qparser.Lexbuf.from_lexbuf ~reset_line:parameters.reset_line lexer
  142. in
  143. (* Initialize all the checkers before parsing the source *)
  144. let (module Check) = Lazy.force checkers in
  145. let check_context = Check.initialize () in
  146. let ctx =
  147. ref { error_nb = 0; warn_nb = 0; debug_nb = 0; fatal_error = false }
  148. in
  149. let () =
  150. try
  151. while true do
  152. parse_location ~ctx
  153. (module Check)
  154. check_context lexer parameters.filters
  155. done
  156. with Qparser.Lexer.EOF -> ()
  157. in
  158. (match !ctx.fatal_error with
  159. | true ->
  160. Format.fprintf Format.std_formatter
  161. "(Ignoring global checkers because of the previous syntax errors)@."
  162. | false ->
  163. (* If the parsing was global and we didn’t got parsing error, extract the
  164. result for the whole test *)
  165. let global_report = Check.finalize check_context in
  166. List.iter global_report ~f:(fun (f_name, report) ->
  167. Format.fprintf Format.std_formatter "Location@ %s@;@[%a@]@." f_name
  168. Report.pp report;
  169. match report.Report.level with
  170. | Error -> ctx := { !ctx with error_nb = succ !ctx.error_nb }
  171. | Warn -> ctx := { !ctx with warn_nb = succ !ctx.warn_nb }
  172. | Debug -> ctx := { !ctx with debug_nb = succ !ctx.debug_nb }));
  173. match (!ctx.error_nb, !ctx.warn_nb) with
  174. | 0, 0 -> (
  175. print_endline "No errors found";
  176. match !ctx.debug_nb with 0 -> exit 0 | _ -> exit 1)
  177. | _ ->
  178. Printf.printf "Found %d error(s), %d warning(s)\n" !ctx.error_nb
  179. !ctx.warn_nb;
  180. exit 1