open Core.Std

type t = {file:string option; line:int option; col:int option}
with sexp

exception Bad of string
let raise_bad msg = raise (Bad msg)

exception Undefined

let assert_well_formed t =
  if Option.is_some t.col && not (Option.is_some t.line) then raise_bad "cannot set column number without line number"

let make ?file ?line ?col () = {file; line; col}
    
let f s = {file=Some s; line=None; col=None}
let l k = {file=None; line=Some k; col=None}
let fl s k = {file=Some s; line=Some k; col=None}
let lc k1 k2 = {file=None; line=Some k1; col=Some k2}
let flc s k1 k2 = {file=Some s; line=Some k1; col=Some k2}
let unknown = {file=None; line=None; col=None}

let file_exn t = match t.file with Some s -> s | None -> raise Undefined
let line_exn t = match t.line with Some s -> s | None -> raise Undefined
let col_exn t = match t.col with Some s -> s | None -> raise Undefined

let set_file t s = let ans = {t with file = Some s} in assert_well_formed ans; ans
let set_line t k = let ans = {t with line = Some k} in assert_well_formed ans; ans
let set_col t k = let ans = {t with col = Some k} in assert_well_formed ans; ans

let incrl t k =
  match t.line with
      None -> raise Undefined
    | Some l -> {t with line = Some (l+k)}

let to_string t =
  if Option.is_none t.file && Option.is_none t.line && Option.is_none t.col then
    "unknown_position"
  else
    let f =
      match t.file with 
          None -> "" 
        | Some s -> (match t.line with None -> s | Some _ -> s ^ ":")
    in
    
    let l =
      match t.line with
          None -> "" 
        | Some k -> (match t.col with None -> string_of_int k | Some _ -> string_of_int k ^ ".")
    in
    
    let c =
      match t.col with
          None -> ""               
        | Some k -> string_of_int k
    in
    f ^ l ^ c