lexer.ml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. (**
  2. Lexer using sedlex
  3. *)
  4. open Tokens
  5. open StdLabels
  6. exception UnclosedQuote
  7. exception LexError of string
  8. exception EOF
  9. (* Extract the location name from the pattern *)
  10. let location_name = Str.regexp {|# *\(.*\)|}
  11. (** Remove all the expression state when we are leaving the expression itself. *)
  12. let rec leave_expression buffer =
  13. match Lexbuf.state buffer with
  14. | Some Lexbuf.Expression ->
  15. Lexbuf.leave_state buffer;
  16. leave_expression buffer
  17. | _ -> ()
  18. (** Try to read the identifier and check if this is a function, a keyword, or
  19. just a variable.
  20. See the tests [Syntax.Operator2] and [Syntax.Call Nl] for two cases. *)
  21. let build_ident buffer =
  22. let id = Lexbuf.content buffer |> String.uppercase_ascii in
  23. try
  24. let value = Hashtbl.find Idents.keyword_table id in
  25. let _ =
  26. match value with
  27. | IF | ELIF -> Lexbuf.enter_state buffer Lexbuf.Expression
  28. | _ -> ()
  29. in
  30. value
  31. with Not_found ->
  32. (* If the identifier does not match a keyword and start with [*], then
  33. try it as a '*' operator. *)
  34. if Char.equal '*' id.[0] then (
  35. Lexbuf.rollback buffer;
  36. let lexbuf = Lexbuf.buffer buffer in
  37. match%sedlex lexbuf with '*' -> STAR | _ -> IDENT id)
  38. else IDENT id
  39. let wait_balance : (Buffer.t -> Lexbuf.t -> 'a) -> Lexbuf.t -> 'a =
  40. fun rule lexbuf ->
  41. let _, position = Lexbuf.positions lexbuf in
  42. Lexbuf.set_start_position lexbuf position;
  43. try
  44. let token = rule (Buffer.create 256) lexbuf in
  45. token
  46. with Not_found -> raise UnclosedQuote
  47. let space = [%sedlex.regexp? ' ' | '\t']
  48. let eol = [%sedlex.regexp? '\r' | '\n' | "\r\n"]
  49. let coma = [%sedlex.regexp? ',']
  50. let digit = [%sedlex.regexp? '0' .. '9']
  51. let letters = [%sedlex.regexp? 'a' .. 'z' | 'A' .. 'Z' | '_']
  52. let ident = [%sedlex.regexp? Opt ('$' | '*'), letters, Star (digit | letters)]
  53. let location_ident = [%sedlex.regexp? letters | digit]
  54. let location_prefix = [%sedlex.regexp? '!' | '$' | '#' | '^']
  55. let location = [%sedlex.regexp? Opt location_prefix, Plus location_ident]
  56. (** Read a quote started with '{'
  57. The function return the parsed string, but the closing token has been
  58. rollbacked, leaving the state in [Lexbuf.EndString _].
  59. The next call to [main] will call the associated function, effectively
  60. leaving the string mode in the parser.
  61. @param nested tell with started another block of string inside this one *)
  62. let rec read_long_string : ?nested:bool -> int -> Buffer.t -> Lexbuf.t -> token
  63. =
  64. fun ?(nested = false) level buf buffer ->
  65. let lexbuf = Lexbuf.buffer buffer in
  66. match%sedlex lexbuf with
  67. | "<<" ->
  68. if not nested then (
  69. match Buffer.length buf with
  70. | 0 ->
  71. Lexbuf.enter_state buffer Lexbuf.Token;
  72. ENTER_EMBED
  73. | _ ->
  74. let result = Tokens.LITERAL (Buffer.contents buf) in
  75. Buffer.reset buf;
  76. Lexbuf.rollback buffer;
  77. result)
  78. else (
  79. Buffer.add_string buf (Lexbuf.content buffer);
  80. read_long_string ~nested level buf buffer)
  81. | '{' ->
  82. Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf);
  83. read_long_string ~nested (level + 1) buf buffer
  84. | '}' -> (
  85. match level with
  86. | 0 ->
  87. Lexbuf.leave_state buffer;
  88. Lexbuf.enter_state buffer
  89. (Lexbuf.EndString Lex_state.readLongStringWraper);
  90. (* rollback the latest token *)
  91. Lexbuf.rollback buffer;
  92. LITERAL (Buffer.contents buf)
  93. | _ ->
  94. (* We have nested strings. Do not terminate end *)
  95. Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf);
  96. read_long_string ~nested (level - 1) buf buffer)
  97. | '\'' | '"' ->
  98. Buffer.add_string buf (Lexbuf.content buffer);
  99. read_long_string ~nested:true level buf buffer
  100. | eol ->
  101. Buffer.add_string buf (Lexbuf.content buffer);
  102. read_long_string ~nested level buf buffer
  103. | any ->
  104. Buffer.add_string buf (Lexbuf.content buffer);
  105. read_long_string ~nested level buf buffer
  106. | _ -> raise Not_found
  107. (** Read the text inside a ['] or ['"']
  108. The function return the parsed string, but the closing token has been
  109. rollbacked, leaving the state in [Lexbuf.EndString _].
  110. The next call to [main] will call the associated function, effectively
  111. leaving the string mode in the parser.
  112. *)
  113. let rec read_quoted_string : Lexbuf.stringWraper -> Lexbuf.buffer_builder =
  114. fun f ?(nested = false) buf buffer ->
  115. let lexbuf = Lexbuf.buffer buffer in
  116. match%sedlex lexbuf with
  117. | "<<" ->
  118. (* Enter into embed code. We enter here into a new state until the
  119. matching >>.
  120. If we already got some text before, report the literal token, then
  121. rollback to read the embeded code again. *)
  122. if not nested then (
  123. match Buffer.length buf with
  124. | 0 ->
  125. Lexbuf.enter_state buffer Lexbuf.Token;
  126. ENTER_EMBED
  127. | _ ->
  128. let result = Tokens.LITERAL (Buffer.contents buf) in
  129. Buffer.reset buf;
  130. Lexbuf.rollback buffer;
  131. result)
  132. else (
  133. Buffer.add_string buf (Lexbuf.content buffer);
  134. (f.wrap (read_quoted_string f)) buf buffer)
  135. | eol | any ->
  136. Buffer.add_string buf (Lexbuf.content buffer);
  137. (f.wrap (read_quoted_string f)) buf buffer
  138. | _ -> raise Not_found
  139. (** Track the kind of nested string inside a multiline string inside a
  140. comment.
  141. Some constructions are not allowed in this specific case (see later)
  142. *)
  143. type commentedString = None | Quote | DQuote
  144. let rec skip_comment buffer =
  145. (* Simplified way to skip the content of a string until the end marker.
  146. (expect the string to be well formed) *)
  147. let rec parse_until_end : (Buffer.t -> Lexbuf.t -> token) -> token =
  148. fun f ->
  149. let token = wait_balance f buffer in
  150. match Lexbuf.state buffer with
  151. | Some Lexbuf.Token ->
  152. Lexbuf.leave_state buffer;
  153. parse_until_end f
  154. | Some (Lexbuf.EndString _) -> token
  155. | _ -> parse_until_end f
  156. in
  157. let lexbuf = Lexbuf.buffer buffer in
  158. match%sedlex lexbuf with
  159. | '{' ->
  160. (* There are illegal constructions inside a comment containing {}
  161. block.
  162. Every opening text marker shall have the corresponding closing token
  163. (but inside a nested block we can have unmatched double quote).
  164. [! { ' }] this gives an error
  165. [! { ' ' }] is ok
  166. *)
  167. let token = parse_until_end (read_long_string 0) in
  168. let () =
  169. match token with
  170. | Tokens.LITERAL (content : string) -> (
  171. (* Ensure every opening quote is closed before the end of the
  172. comment *)
  173. let string_state =
  174. String.fold_left content ~init:None ~f:(fun state c ->
  175. match (state, c) with
  176. | None, '\'' -> Quote
  177. | None, '"' -> DQuote
  178. | Quote, '\'' -> None
  179. | DQuote, '"' -> None
  180. | _ -> state)
  181. in
  182. match string_state with
  183. | None -> ()
  184. | _ ->
  185. Lexbuf.leave_state buffer;
  186. raise UnclosedQuote)
  187. | _ -> ()
  188. in
  189. let () =
  190. try ignore (Lex_state.readLongStringWraper.end_string buffer)
  191. with _ -> ()
  192. in
  193. skip_comment buffer
  194. | '\'' ->
  195. let _ =
  196. parse_until_end
  197. (Lex_state.quotedStringWraper.wrap
  198. (read_quoted_string Lex_state.quotedStringWraper))
  199. in
  200. let _ = Lex_state.quotedStringWraper.end_string buffer in
  201. skip_comment buffer
  202. | '"' ->
  203. let _ =
  204. parse_until_end
  205. (Lex_state.dQuotedStringWraper.wrap
  206. (read_quoted_string Lex_state.dQuotedStringWraper))
  207. in
  208. let _ = Lex_state.dQuotedStringWraper.end_string buffer in
  209. skip_comment buffer
  210. | eol ->
  211. (* Ugly hack used in order to put the eol in the front of the next
  212. parsing.
  213. This is required because the eol is also a part of the syntax, and do
  214. cannot be discard with the end of the comment. *)
  215. Lexbuf.rollback buffer;
  216. COMMENT
  217. | any -> skip_comment buffer
  218. | _ -> raise Not_found
  219. (** Main lexer *)
  220. let rec parse_token : Lexbuf.t -> token =
  221. fun buffer ->
  222. let lexbuf = Lexbuf.buffer buffer in
  223. match%sedlex lexbuf with
  224. | 0Xfeff ->
  225. (* Ignore the BOM *)
  226. parse_token buffer
  227. | '#', Star space, location ->
  228. (* Extract the location name *)
  229. let ident = Lexbuf.content buffer in
  230. let ident_name =
  231. match Str.string_match location_name ident 0 with
  232. | false -> ident
  233. | true -> Str.matched_group 1 ident
  234. in
  235. (* Restart the line number (new location here) *)
  236. Lexbuf.start buffer;
  237. LOCATION_START
  238. (fun () ->
  239. Sedlexing.set_filename lexbuf ident_name;
  240. (* Restart the line number (new location here) *)
  241. ident_name)
  242. | '_', Star space, eol, Star space ->
  243. (* The _ character can be used to break lines *)
  244. parse_token buffer
  245. | '-', Plus '-', Star (Sub (any, ('\r' | '\n'))) ->
  246. leave_expression buffer;
  247. LOCATION_END
  248. | Plus digit -> INTEGER (Lexbuf.content buffer)
  249. | '+' -> PLUS
  250. | '-' -> MINUS
  251. | "+=" -> INCR
  252. | "-=" -> DECR
  253. | "*=" -> MULT_EQUAL
  254. | "/=" -> DIV_EQUAL
  255. | '/' -> DIV
  256. | '*' -> STAR
  257. | ':' ->
  258. (* We are leaving the block, the comment will be handled again *)
  259. Lexbuf.leave_state buffer;
  260. COLUMN
  261. | '[' -> L_BRACKET
  262. | ']' -> R_BRACKET
  263. | '(' ->
  264. Lexbuf.enter_state buffer Lexbuf.Expression;
  265. L_PAREN
  266. | ')' ->
  267. Lexbuf.leave_state buffer;
  268. R_PAREN
  269. | ">>" ->
  270. (* Leave the expression if we have any*)
  271. leave_expression buffer;
  272. (* Now leave the token mode and return to the string *)
  273. Lexbuf.leave_state buffer;
  274. LEAVE_EMBED
  275. | '<' -> LT
  276. | '>' -> GT
  277. | coma -> COMA
  278. | '=' ->
  279. Lexbuf.enter_state buffer Lexbuf.Expression;
  280. EQUAL
  281. | ident -> build_ident buffer
  282. | eol ->
  283. leave_expression buffer;
  284. EOL
  285. | '&' ->
  286. leave_expression buffer;
  287. AMPERSAND
  288. | '!' -> (
  289. match Lexbuf.state buffer with
  290. | Some Lexbuf.Expression -> EXCLAMATION
  291. | _ -> skip_comment buffer)
  292. | '}' -> TEXT_MARKER
  293. | eof -> raise EOF
  294. | _ ->
  295. let tok = Lexbuf.content buffer in
  296. let msg = Format.asprintf "Unexpected character %S" tok in
  297. raise @@ LexError msg
  298. let main buffer =
  299. match Lexbuf.state buffer with
  300. | Some (Lexbuf.String w) ->
  301. wait_balance (w.wrap @@ read_quoted_string w) buffer
  302. | Some (Lexbuf.MString level) -> wait_balance (read_long_string level) buffer
  303. | Some (Lexbuf.EndString w) -> w.end_string buffer
  304. | _ ->
  305. let parser =
  306. parse_token |> Lex_state.defaultWraper.start_string
  307. |> Lexbuf.overlay buffer
  308. in
  309. parser buffer
  310. let rec discard buffer =
  311. let () = Lexbuf.start_recovery buffer in
  312. let lexbuf = Lexbuf.buffer buffer in
  313. match%sedlex lexbuf with
  314. | '-', Plus '-', Star (Sub (any, ('\r' | '\n'))) ->
  315. (* If something looks like the end of a location, get out the discard
  316. mode.
  317. We can’t really be sure if it is effectively the end of a
  318. location (because we can be in a text), but trying to figure if it is
  319. or not just don’t make sense.
  320. We are here because an error was raised, so can have any situation
  321. (for example a missing quote). *)
  322. leave_expression buffer;
  323. ()
  324. | any -> discard buffer
  325. | _ -> raise EOF