syntax_error.ml 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. module Ast = Qsp_syntax.Tree.Ast
  2. module S = Qsp_syntax.S
  3. let _position = (Lexing.dummy_pos, Lexing.dummy_pos)
  4. type 'a report = { level : Qsp_syntax.Report.level; loc : 'a; message : string }
  5. [@@deriving eq, show]
  6. let report : S.pos report Alcotest.testable =
  7. let equal = equal_report (fun _ _ -> true) in
  8. let pp = pp_report (fun formater _ -> Format.fprintf formater "_position") in
  9. Alcotest.testable pp equal
  10. let get_report :
  11. (S.pos Syntax.location, Qsp_syntax.Report.t) result -> S.pos report =
  12. function
  13. | Ok _ -> failwith "No error"
  14. | Error { level; loc; message } -> { level; loc; message }
  15. let _test_instruction :
  16. ?k:(S.pos report -> unit) -> string -> S.pos report -> unit =
  17. fun ?k literal expected ->
  18. let _location = Printf.sprintf {|# Location
  19. %s
  20. ------- |} literal in
  21. let actual = get_report @@ Syntax.parse _location and msg = literal in
  22. let () = Alcotest.(check' report ~msg ~expected ~actual) in
  23. match k with None -> () | Some f -> f actual
  24. let else_column () =
  25. _test_instruction
  26. {|IF 1:
  27. 0
  28. ELSE:
  29. 1
  30. END|}
  31. {
  32. level = Error;
  33. loc = _position;
  34. message = "Unexpected operator after `ELSE`";
  35. }
  36. let elseif_no_column () =
  37. _test_instruction
  38. {|IF 1:
  39. 0
  40. ELSEIF 0
  41. 1
  42. END|}
  43. {
  44. level = Error;
  45. loc = _position;
  46. message =
  47. "The `ELIF` expression does not end properly. A `:` is expected before \
  48. any instruction.";
  49. }
  50. let unclosed_paren () =
  51. _test_instruction
  52. {|(1
  53. |}
  54. {
  55. level = Error;
  56. loc = _position;
  57. message = "Unexpected '('. Did you forgot a function before ?";
  58. }
  59. let act_no_column () =
  60. _test_instruction
  61. {|ACT 1
  62. 0
  63. END|}
  64. {
  65. level = Error;
  66. loc = _position;
  67. message = "Invalid `ACT` label. You probably missed a ':'";
  68. }
  69. let missing_ampersand () =
  70. let result =
  71. {
  72. level = Error;
  73. loc = _position;
  74. message = "Missing separator between instructions";
  75. }
  76. in
  77. let () = _test_instruction "b = 1 a = 2" result
  78. and () = _test_instruction "let b = 1 a = 2" result
  79. and () = _test_instruction "set b = 1 a = 2" result in
  80. ()
  81. let unclose_comment () =
  82. _test_instruction {| ! that's it|}
  83. { level = Error; loc = _position; message = "Unclosed string" }
  84. let syntax_error () =
  85. _test_instruction {|*clr $ cla|}
  86. { level = Error; loc = _position; message = "Unexpected character \"\"" }
  87. let missing_operand () =
  88. let result =
  89. { level = Error; loc = _position; message = "Missing operand" }
  90. in
  91. let () = _test_instruction {|if and other: 1|} result
  92. and () = _test_instruction {|a = and other: 1|} result in
  93. ()
  94. let unknow_function () =
  95. _test_instruction "a = ran(1, 2)"
  96. { level = Error; loc = _position; message = "Unexpected expression here." }
  97. let inline_elif () =
  98. _test_instruction {|
  99. if a = 1:
  100. elseif a = 1: a = 1
  101. end|}
  102. {
  103. level = Error;
  104. loc = _position;
  105. message = "Mix between `IF` block and inline `ELIF`";
  106. }
  107. let unclosed_block () =
  108. _test_instruction {|
  109. if $ARGS[0] = 'arg':
  110. act'action':
  111. end|}
  112. {
  113. level = Error;
  114. loc = _position;
  115. message =
  116. "Unclosed `IF` block. Another block ends before the `END` instruction.";
  117. };
  118. _test_instruction
  119. {|
  120. IF 1:
  121. ELSE
  122. 0
  123. |}
  124. {
  125. level = Error;
  126. loc = _position;
  127. message =
  128. "Unclosed `ELSE` block. Another block ends before the `END` \
  129. instruction.";
  130. };
  131. _test_instruction
  132. {|
  133. IF 1:
  134. ELSEIF 0:
  135. 0
  136. |}
  137. {
  138. level = Error;
  139. loc = _position;
  140. message =
  141. "Unclosed `ELIF` block. Another block ends before the `END` \
  142. instruction.";
  143. }
  144. let comment_as_operator () =
  145. let result =
  146. {
  147. level = Error;
  148. loc = _position;
  149. message = "Missing separator between instructions";
  150. }
  151. in
  152. _test_instruction "gs 'a', 'b' ! target" result;
  153. _test_instruction "gs 'a' ! target" result
  154. let missing_comparable () =
  155. let result =
  156. {
  157. level = Error;
  158. loc = _position;
  159. message = "Missing boolean after operator";
  160. }
  161. in
  162. _test_instruction "1 and >= 0" result;
  163. _test_instruction "1 >= and 0" result;
  164. _test_instruction "1 > and 0" result;
  165. _test_instruction "1 < and 0" result;
  166. _test_instruction "1 or >= 0" result;
  167. _test_instruction "1 >= or 0" result;
  168. _test_instruction "1 <= or 0" result;
  169. _test_instruction "1 = or 0" result
  170. (** This code looks like a new location, but is actualy invalid.
  171. The application should report the old location.
  172. *)
  173. let location_change () =
  174. let result =
  175. {
  176. level = Error;
  177. loc = _position;
  178. message = "Missing boolean after operator";
  179. }
  180. in
  181. _test_instruction "1 and >= # invalid" result ~k:(fun actual ->
  182. let actual = (fst actual.loc).Lexing.pos_fname in
  183. Alcotest.(
  184. check' ~msg:"The location name is not valid" string ~actual
  185. ~expected:"Location"))
  186. let misplaced_if () =
  187. _test_instruction {|
  188. if $ARGS[0] = 'arg':
  189. 0
  190. end if|}
  191. {
  192. level = Error;
  193. loc = _position;
  194. message = "Unexpected instruction after `IF` `END` block.";
  195. };
  196. _test_instruction {|
  197. act 'arg':
  198. 0
  199. end if|}
  200. {
  201. level = Error;
  202. loc = _position;
  203. message = "Unexpected instruction after `ACT` `END` block.";
  204. }
  205. (* The location name *)
  206. let unclosed_paren2 () =
  207. _test_instruction {|
  208. iif(1,0,0 + iif(1, 1, 2)
  209. |}
  210. { level = Error; loc = _position; message = "Unclosed `(`" }
  211. let unclosed_act () =
  212. _test_instruction {|
  213. act 'Smthg':
  214. else
  215. end
  216. end|}
  217. {
  218. level = Error;
  219. loc = _position;
  220. message =
  221. "A block starting with `ACT` is not closed by `END`\n\
  222. If there are nested blocks, the error will points the highest block.";
  223. }
  224. let unknown_operator () =
  225. _test_instruction
  226. {|
  227. variable + = 1
  228. |}
  229. {
  230. level = Error;
  231. loc = _position;
  232. message = "Unknown operator. Did you write '+ =' instead of '+=' ?";
  233. }
  234. let nested_string_mess () =
  235. _test_instruction
  236. {|
  237. '<<func('…', '…')>>'
  238. |}
  239. { level = Error; loc = _position; message = "Unclosed string" }
  240. let test =
  241. ( "Syntax Errors",
  242. [
  243. Alcotest.test_case "else:" `Quick else_column;
  244. Alcotest.test_case "elseif" `Quick elseif_no_column;
  245. Alcotest.test_case "(1" `Quick unclosed_paren;
  246. Alcotest.test_case "act 1" `Quick act_no_column;
  247. Alcotest.test_case "no &" `Quick missing_ampersand;
  248. Alcotest.test_case "unclose_comment" `Quick unclose_comment;
  249. Alcotest.test_case "Syntax error $" `Quick syntax_error;
  250. Alcotest.test_case "Missing operand" `Quick missing_operand;
  251. Alcotest.test_case "Unknown function" `Quick unknow_function;
  252. Alcotest.test_case "Inline elif" `Quick inline_elif;
  253. Alcotest.test_case "Unclosed block" `Quick unclosed_block;
  254. Alcotest.test_case "Unclosed block" `Quick comment_as_operator;
  255. Alcotest.test_case "Missing comparable" `Quick missing_comparable;
  256. Alcotest.test_case "Location change" `Quick location_change;
  257. Alcotest.test_case "Misplaced if" `Quick misplaced_if;
  258. Alcotest.test_case "(()" `Quick unclosed_paren2;
  259. Alcotest.test_case "act: else" `Quick unclosed_act;
  260. Alcotest.test_case "+ =" `Quick unknown_operator;
  261. Alcotest.test_case "'<<''>>'" `Quick nested_string_mess;
  262. ] )