qsp_parser.ml 6.8 KB

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