(** Lexer using sedlex *) open Tokens exception UnclosedQuote of { content : string; line : int } exception LexError of Lexing.position * string exception EOF let pp_pos out { Lexing.pos_lnum; pos_cnum; pos_bol; _ } = Format.fprintf out "line %d:%d" pos_lnum (pos_cnum - pos_bol) (* Extract the location name from the pattern *) let location_name = Str.regexp {|.* \(.*\)|} module type Encoding = sig val lexeme : Sedlexing.lexbuf -> string end (* The comment system is terrible. The same symbol can be used for : - starting a comment - inequality operation In order to manage this, I try to identify the context in a very basic way, using a flag True False for determining the token to send. *) module Bucket = Ephemeron.K1.Bucket type bucket = (Sedlexing.lexbuf, int) Bucket.t let is_expression : bucket = Bucket.make () let incr_level lexbuf = match Bucket.find is_expression lexbuf with | None -> Bucket.add is_expression lexbuf 1 | Some v -> Bucket.add is_expression lexbuf (v + 1) let decr_level lexbuf = match Bucket.find is_expression lexbuf with | None -> () | Some v -> if v > 1 then Bucket.add is_expression lexbuf (v - 1) else Bucket.remove is_expression lexbuf let build_ident lexbuf = let id = Sedlexing.lexeme lexbuf |> Idents.of_uchars |> String.uppercase_ascii in try let value = Hashtbl.find Idents.keyword_table id in let _ = match value with IF | ELIF -> incr_level lexbuf | _ -> () in value with Not_found -> IDENT id let incr_level lexbuf = match Bucket.find is_expression lexbuf with | None -> Bucket.add is_expression lexbuf 1 | Some v -> Bucket.add is_expression lexbuf (v + 1) let wait_balance : (Buffer.t -> Lexbuf.t -> 'a) -> Lexbuf.t -> 'a = fun rule lexbuf -> try[@warning "-52"] let token = rule (Buffer.create 256) lexbuf in token with Failure "lexing: empty token" -> let position, _ = Lexbuf.positions lexbuf in let line = position.Lexing.pos_lnum and content = Lexbuf.content lexbuf in (raise (UnclosedQuote { line; content }) [@warning "+52"]) let space = [%sedlex.regexp? ' ' | '\t'] let eol = [%sedlex.regexp? '\r' | '\n' | "\r\n"] let coma = [%sedlex.regexp? ','] let digit = [%sedlex.regexp? '0' .. '9'] let letters = [%sedlex.regexp? 'a' .. 'z' | 'A' .. 'Z' | '_'] let spaces = [%sedlex.regexp? Plus space] let ident = [%sedlex.regexp? ('$' | letters), Star (digit | letters)] let location_ident = [%sedlex.regexp? letters | digit] let location_prefix = [%sedlex.regexp? '!' | '$' | '#' | '^'] let location = [%sedlex.regexp? Opt location_prefix, Plus location_ident] let rec read_long_string level buf buffer = let lexbuf = Lexbuf.buffer buffer in match%sedlex lexbuf with | '{' -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_long_string (level + 1) buf buffer | '}' -> ( match level with | 0 -> Buffer.contents buf | _ -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_long_string (level - 1) buf buffer) | eol -> Buffer.add_string buf (Lexbuf.content buffer); read_long_string level buf buffer | any -> Buffer.add_string buf (Lexbuf.content buffer); read_long_string level buf buffer | _ -> raise Not_found let rec read_dquoted_string buf buffer = let lexbuf = Lexbuf.buffer buffer in match%sedlex lexbuf with | "\"\"" -> Buffer.add_char buf '"'; read_dquoted_string buf buffer | '"' -> Buffer.contents buf | any -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_dquoted_string buf buffer | _ -> raise Not_found let rec read_quoted_string buf buffer = let lexbuf = Lexbuf.buffer buffer in match%sedlex lexbuf with | "''" -> Buffer.add_char buf '\''; read_quoted_string buf buffer | '\'' -> Buffer.contents buf | eol -> Buffer.add_string buf (Lexbuf.content buffer); read_quoted_string buf buffer | any -> Buffer.add_string buf (Lexbuf.content buffer); read_quoted_string buf buffer | _ -> raise Not_found let rec skip_comment buffer = let lexbuf = Lexbuf.buffer buffer in match%sedlex lexbuf with | '{' -> let _ = wait_balance (read_long_string 0) buffer in skip_comment buffer | '\'' -> let _ = wait_balance read_quoted_string buffer in skip_comment buffer | '"' -> let _ = wait_balance read_dquoted_string buffer in skip_comment buffer | eol -> (* Ugly hack used in order to put the eol in the front of the next parsing. *) Sedlexing.rollback lexbuf; COMMENT | any -> skip_comment buffer | _ -> raise Not_found (** Main lexer *) let rec token : Lexbuf.t -> token = fun buffer -> let lexbuf = Lexbuf.buffer buffer in match%sedlex lexbuf with | 0Xfeff -> Lexbuf.start buffer; (* Ignore the BOM *) token buffer | '#', Star space, location -> let _start_pos, end_pos = Sedlexing.lexing_positions lexbuf in (* Extract the location name *) let ident = Lexbuf.content buffer in let () = match Str.string_match location_name ident 0 with | false -> () | true -> Sedlexing.set_filename lexbuf (Str.matched_group 1 ident) in (* Restart the line number (new location here) *) Sedlexing.start lexbuf; Sedlexing.set_position lexbuf { end_pos with Lexing.pos_lnum = 1 }; LOCATION_START ident | '-', Plus '-', Star (Sub (any, ('\r' | '\n'))) -> Bucket.remove is_expression lexbuf; LOCATION_END | Plus digit -> INTEGER (Lexbuf.content buffer) | '+' -> PLUS | '-' -> MINUS | "+=" -> INCR | "-=" -> DECR | "*=" -> MULT_EQUAL | '/' -> DIV | '*' -> STAR | ':' -> (* We are leaving the block, the comment will be handled again *) decr_level lexbuf; COLUMN | '[' -> L_BRACKET | ']' -> R_BRACKET | '(' -> incr_level lexbuf; L_PAREN | ')' -> decr_level lexbuf; R_PAREN | '<' -> LT | '>' -> GT | coma -> COMA | '=' -> incr_level lexbuf; EQUAL | ident -> build_ident lexbuf | eol -> Bucket.add is_expression lexbuf 0; EOL | '&' -> Bucket.add is_expression lexbuf 0; AMPERSAND | '!' -> ( match Bucket.find is_expression lexbuf with | Some i when i <> 0 -> EXCLAMATION | _ -> skip_comment buffer) | spaces -> token buffer | '\'' -> LITERAL (wait_balance read_quoted_string buffer) | '"' -> LITERAL (wait_balance read_dquoted_string buffer) | '{' -> LITERAL (wait_balance (read_long_string 0) buffer) | eof -> raise EOF | _ -> let position = fst @@ Sedlexing.lexing_positions lexbuf in let tok = Lexbuf.content buffer in let msg = Format.asprintf "Unexpected character %S at %a" tok pp_pos position in raise @@ LexError (position, msg) let rec discard buffer = let lexbuf = Lexbuf.buffer buffer in match%sedlex lexbuf with | '\'' -> ignore (wait_balance read_quoted_string buffer); discard buffer | '"' -> ignore (wait_balance read_dquoted_string buffer); discard buffer | '{' -> ignore (wait_balance (read_long_string 0) buffer); discard buffer | eof -> raise EOF | '-', Plus '-', Star (Sub (any, ('\r' | '\n'))) -> Bucket.remove is_expression lexbuf; () | '!' -> ignore @@ skip_comment buffer; discard buffer | any -> discard buffer | _ -> raise EOF