dead_end.ml 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. open StdLabels
  2. let identifier = "dead_end"
  3. let description = "Check for dead end in the code"
  4. let active = ref false
  5. module Expression = struct
  6. type t = unit
  7. type t' = unit
  8. include Default.Expression (struct
  9. type nonrec t = t
  10. let default = ()
  11. end)
  12. let v : t -> t' = fun () -> ()
  13. end
  14. module Instruction = struct
  15. type expression = Expression.t'
  16. type cause = Missing_else | Unchecked_path
  17. type state = {
  18. block_pos : S.pos;
  19. has_gt : bool;
  20. is_gt : bool;
  21. pos : (cause * S.pos) option;
  22. }
  23. type t = state
  24. type t' = state
  25. (** For each instruction, return thoses two informations :
  26. - the intruction contains at [gt]
  27. - the last instruction is a [gt]
  28. *)
  29. let v : t -> t' = fun t -> t
  30. let default =
  31. {
  32. block_pos = (Lexing.dummy_pos, Lexing.dummy_pos);
  33. has_gt = false;
  34. is_gt = false;
  35. pos = None;
  36. }
  37. (** Call for an instruction like [GT] or [*CLR] *)
  38. let call : S.pos -> T.keywords -> expression list -> t =
  39. fun pos f _ ->
  40. ignore pos;
  41. match f with
  42. | T.Goto | T.XGoto ->
  43. { block_pos = pos; has_gt = true; is_gt = true; pos = None }
  44. | T.Gosub -> { block_pos = pos; has_gt = false; is_gt = true; pos = None }
  45. | _ -> default
  46. (** Label for a loop *)
  47. let location : S.pos -> string -> t = fun _ _ -> default
  48. (** Comment *)
  49. let comment : S.pos -> t = fun _ -> default
  50. (** Raw expression *)
  51. let expression : expression -> t = fun _ -> default
  52. (** The content of a block is very linear, I only need to check the last element *)
  53. let check_block : S.pos -> t list -> t =
  54. fun pos instructions ->
  55. let last_element =
  56. List.fold_left instructions ~init:default ~f:(fun t instruction ->
  57. let result = instruction in
  58. let has_gt = result.has_gt || t.has_gt in
  59. let is_gt = result.is_gt || t.is_gt in
  60. { result with block_pos = pos; is_gt; has_gt })
  61. in
  62. last_element
  63. let if_ :
  64. S.pos ->
  65. (expression, t) S.clause ->
  66. elifs:(expression, t) S.clause list ->
  67. else_:(S.pos * t list) option ->
  68. t =
  69. fun pos clause ~elifs ~else_ ->
  70. (* For each block, evaluate the instructions *)
  71. let res, has_gt, is_gt =
  72. List.fold_left ~init:([], false, false) (clause :: elifs)
  73. ~f:(fun (acc, has_gt, is_gt) clause ->
  74. let pos, _, instructions = clause in
  75. let clause_t = check_block pos instructions in
  76. let has_gt = has_gt || clause_t.has_gt
  77. and is_gt = is_gt || clause_t.is_gt in
  78. ((clause_t, pos) :: acc, has_gt, is_gt))
  79. in
  80. let else_pos, else_block =
  81. match else_ with
  82. | Some (pos, instructions) ->
  83. let block = check_block pos instructions in
  84. (pos, block)
  85. | None -> (pos, default)
  86. in
  87. let has_gt = has_gt || else_block.has_gt
  88. and is_gt = is_gt || else_block.is_gt in
  89. let blocks = (else_block, else_pos) :: res in
  90. (* Check if one of the clauses already holds a dead end*)
  91. match List.find_opt res ~f:(fun (res, _) -> res.pos != None) with
  92. | Some (v, _) -> v
  93. | None -> (
  94. match (is_gt, has_gt) with
  95. | _, true -> (
  96. (* There is gt intruction in one of the branch, we need to checks
  97. the others *)
  98. match List.find_opt blocks ~f:(fun (f, _) -> not f.is_gt) with
  99. | None ->
  100. (* Every branch in the if is covered. It’s ok. *)
  101. { default with block_pos = pos; is_gt; has_gt }
  102. | Some (_, pos) ->
  103. (* TODO check if [pos] is the whole block *)
  104. let cause =
  105. match else_ with None -> Missing_else | _ -> Unchecked_path
  106. in
  107. { default with block_pos = pos; pos = Some (cause, pos) })
  108. | _, _ -> { default with block_pos = pos; has_gt; is_gt })
  109. let act : S.pos -> label:expression -> t list -> t =
  110. fun pos ~label expressions ->
  111. ignore label;
  112. check_block pos expressions
  113. let assign :
  114. S.pos ->
  115. (S.pos, expression) S.variable ->
  116. T.assignation_operator ->
  117. expression ->
  118. t =
  119. fun _ _ _ _ -> default
  120. end
  121. module Location = struct
  122. type t = Report.t list
  123. type instruction = Instruction.t'
  124. let v = Fun.id
  125. let location : S.pos -> instruction list -> t =
  126. fun _pos instructions ->
  127. List.fold_left instructions ~init:[] ~f:(fun report t ->
  128. match (t.Instruction.is_gt, t.Instruction.pos) with
  129. | false, Some (cause, value) ->
  130. ignore cause;
  131. if t.Instruction.block_pos != value then
  132. match cause with
  133. | Missing_else ->
  134. Report.debug value "Possible dead end (no else fallback)"
  135. :: report
  136. | Unchecked_path ->
  137. Report.warn value "Possible dead end (unmatched path)"
  138. :: report
  139. else report
  140. | _ -> report)
  141. end