syntax_error.ml 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. (* Same but with nested string *)
  85. let unclose_comment2 () =
  86. _test_instruction {| !{ that's it }|}
  87. { level = Error; loc = _position; message = "Unclosed string" }
  88. let syntax_error () =
  89. _test_instruction {|*clr $ cla|}
  90. { level = Error; loc = _position; message = "Unexpected character \"\"" }
  91. let missing_operand () =
  92. let result =
  93. { level = Error; loc = _position; message = "Missing operand" }
  94. in
  95. let () = _test_instruction {|if and other: 1|} result
  96. and () = _test_instruction {|a = and other: 1|} result in
  97. ()
  98. let unknow_function () =
  99. _test_instruction "a = ran(1, 2)"
  100. { level = Error; loc = _position; message = "Unexpected expression here." }
  101. let inline_elif () =
  102. _test_instruction {|
  103. if a = 1:
  104. elseif a = 1: a = 1
  105. end|}
  106. {
  107. level = Error;
  108. loc = _position;
  109. message = "Mix between `IF` block and inline `ELIF`";
  110. }
  111. let unclosed_block () =
  112. _test_instruction {|
  113. if $ARGS[0] = 'arg':
  114. act'action':
  115. end|}
  116. {
  117. level = Error;
  118. loc = _position;
  119. message =
  120. "Unclosed `IF` block. Another block ends before the `END` instruction.";
  121. };
  122. _test_instruction
  123. {|
  124. IF 1:
  125. ELSE
  126. 0
  127. |}
  128. {
  129. level = Error;
  130. loc = _position;
  131. message =
  132. "Unclosed `ELSE` block. Another block ends before the `END` \
  133. instruction.";
  134. };
  135. _test_instruction
  136. {|
  137. IF 1:
  138. ELSEIF 0:
  139. 0
  140. |}
  141. {
  142. level = Error;
  143. loc = _position;
  144. message =
  145. "Unclosed `ELIF` block. Another block ends before the `END` \
  146. instruction.";
  147. }
  148. let comment_as_operator () =
  149. let result =
  150. {
  151. level = Error;
  152. loc = _position;
  153. message = "Missing separator between instructions";
  154. }
  155. in
  156. _test_instruction "gs 'a', 'b' ! target" result;
  157. _test_instruction "gs 'a' ! target" result
  158. let missing_comparable () =
  159. let result =
  160. {
  161. level = Error;
  162. loc = _position;
  163. message = "Missing boolean after operator";
  164. }
  165. in
  166. _test_instruction "1 and >= 0" result;
  167. _test_instruction "1 >= and 0" result;
  168. _test_instruction "1 > and 0" result;
  169. _test_instruction "1 < and 0" result;
  170. _test_instruction "1 or >= 0" result;
  171. _test_instruction "1 >= or 0" result;
  172. _test_instruction "1 <= or 0" result;
  173. _test_instruction "1 = or 0" result
  174. (** This code looks like a new location, but is actualy invalid.
  175. The application should report the old location.
  176. *)
  177. let location_change () =
  178. let result =
  179. {
  180. level = Error;
  181. loc = _position;
  182. message = "Missing boolean after operator";
  183. }
  184. in
  185. _test_instruction "1 and >= # invalid" result ~k:(fun actual ->
  186. let actual = (fst actual.loc).Lexing.pos_fname in
  187. Alcotest.(
  188. check' ~msg:"The location name is not valid" string ~actual
  189. ~expected:"Location"))
  190. let misplaced_if () =
  191. _test_instruction {|
  192. if $ARGS[0] = 'arg':
  193. 0
  194. end if|}
  195. {
  196. level = Error;
  197. loc = _position;
  198. message = "Unexpected instruction after `IF` `END` block.";
  199. };
  200. _test_instruction {|
  201. act 'arg':
  202. 0
  203. end if|}
  204. {
  205. level = Error;
  206. loc = _position;
  207. message = "Unexpected instruction after `ACT` `END` block.";
  208. }
  209. (* The location name *)
  210. let unclosed_paren2 () =
  211. _test_instruction {|
  212. iif(1,0,0 + iif(1, 1, 2)
  213. |}
  214. { level = Error; loc = _position; message = "Unclosed `(`" }
  215. let unclosed_act () =
  216. _test_instruction {|
  217. act 'Smthg':
  218. else
  219. end
  220. end|}
  221. {
  222. level = Error;
  223. loc = _position;
  224. message =
  225. "A block starting with `ACT` is not closed by `END`\n\
  226. If there are nested blocks, the error will points the highest block.";
  227. }
  228. let unknown_operator () =
  229. _test_instruction
  230. {|
  231. variable + = 1
  232. |}
  233. {
  234. level = Error;
  235. loc = _position;
  236. message = "Unknown operator. Did you write '+ =' instead of '+=' ?";
  237. }
  238. let nested_string_mess () =
  239. _test_instruction
  240. {|
  241. '<<func('…', '…')>>'
  242. |}
  243. { level = Error; loc = _position; message = "Unclosed string" }
  244. let test =
  245. ( "Syntax Errors",
  246. [
  247. Alcotest.test_case "else:" `Quick else_column;
  248. Alcotest.test_case "elseif" `Quick elseif_no_column;
  249. Alcotest.test_case "(1" `Quick unclosed_paren;
  250. Alcotest.test_case "act 1" `Quick act_no_column;
  251. Alcotest.test_case "no &" `Quick missing_ampersand;
  252. Alcotest.test_case "unclose_comment" `Quick unclose_comment;
  253. Alcotest.test_case "unclose_comment2" `Quick unclose_comment2;
  254. Alcotest.test_case "Syntax error $" `Quick syntax_error;
  255. Alcotest.test_case "Missing operand" `Quick missing_operand;
  256. Alcotest.test_case "Unknown function" `Quick unknow_function;
  257. Alcotest.test_case "Inline elif" `Quick inline_elif;
  258. Alcotest.test_case "Unclosed block" `Quick unclosed_block;
  259. Alcotest.test_case "Unclosed block" `Quick comment_as_operator;
  260. Alcotest.test_case "Missing comparable" `Quick missing_comparable;
  261. Alcotest.test_case "Location change" `Quick location_change;
  262. Alcotest.test_case "Misplaced if" `Quick misplaced_if;
  263. Alcotest.test_case "(()" `Quick unclosed_paren2;
  264. Alcotest.test_case "act: else" `Quick unclosed_act;
  265. Alcotest.test_case "+ =" `Quick unknown_operator;
  266. Alcotest.test_case "'<<''>>'" `Quick nested_string_mess;
  267. ] )