(*
 * This file is part of Coccinelle, licensed under the terms of the GPL v2.
 * See copyright.txt in the Coccinelle source code for more information.
 * The Coccinelle source code can be obtained at https://coccinelle.gitlabpages.inria.fr/website
 *)

(* ---------------------------------------------------------------------- *)
(* useful functions *)

let starts_with c s =
  if String.length s > 0 && String.get s 0 = c
  then Some (String.sub s 1 ((String.length s) - 1))
  else None

let ends_with c s =
  if String.length s > 0 && String.get s ((String.length s) - 1) = c
  then Some (String.sub s 0 ((String.length s) - 1))
  else None

let split_when fn l =
  let rec loop acc = function
  | []    -> raise Not_found
  | x::xs ->
      (match fn x with
	Some x -> List.rev acc, x, xs
      |	None -> loop (x :: acc) xs) in
  loop [] l

(* ---------------------------------------------------------------------- *)
(* make a semantic patch from a string *)

let find_metavariables tokens =
  let rec loop env = function
      [] -> (env,[])
    | x :: xs ->
	(* single upper case letter is a metavariable *)
	let (x,xs,env) =
(*
  Testing for uppercase and length is not enough as "+" is
  a single character identical in upper/lower case.
*)
	  (*
		The ":" delimiter could not be used two times
		1) Str.split
		2) split_when (ends_with ...)

		Otherwise split_when will raise a Not_found exception.
	  *)
	  match Str.bounded_split (Str.regexp ":") x 2 with
	    [before;after] ->
	      let (ty,endty,afterty) = split_when (ends_with ':') (after::xs) in
	      let decl =
		Printf.sprintf "%s %s;\n"
		  (String.concat "" (ty@[endty]))
		  before in
	      (try
		if decl = List.assoc before env
		then (before,afterty,env)
		else failwith (before^" already declared with another type")
	      with Not_found ->
		let env = (before, decl) :: env in
		(before,afterty,env))
	  | _ ->
	      if Str.string_match (Str.regexp "[A-Z]") x 0
	      then
		begin
		  try let _ = Some(List.assoc x env) in (x,xs,env)
		  with Not_found ->
		    let env =
		      (x,(Printf.sprintf "metavariable %s;\n" x)) :: env in
		    (x,xs,env)
		end
	      else (x,xs,env) in
	let (env,sp) = loop env xs in
	(env,x::sp) in
  loop [] tokens

let find_when_dots tokens =
  let rec loop = function
      [] -> []
    | "when !=" :: e :: rest ->
	"when != " :: e :: "\n" :: (loop rest)
    | "when ==" :: e :: rest ->
	"when == " :: e :: "\n" :: (loop rest)
    | "when" :: " " :: e :: rest ->
	"when" :: " " :: e :: "\n" :: (loop rest)
    | "..." :: "when" :: rest -> "\n" :: "..." :: (loop ("when" :: rest))
    | "..." :: rest -> "\n" :: "..." :: "\n" :: (loop rest)
    | x::xs -> x::(loop xs) in
  loop tokens

let add_stars tokens =
  let rec loop = function
      [] -> []
    | "." :: "." :: "." :: rest -> "..." :: skip rest
    | "<" :: "." :: "." :: "." :: rest -> "<..." :: skip rest
    | "<" :: "+" :: "." :: "." :: "." :: rest -> "<+..." :: skip rest
    | "\n" :: rest -> "\n" :: loop rest
    | x :: xs -> ("* " ^ x) :: (skip xs)
  and skip = function
      [] -> []
    | "\n" :: rest -> "\n" :: loop rest
    | x :: xs -> x :: skip xs in
  loop tokens

let rec add_spaces = function
    [] -> []
  | x :: "\n" :: rest -> x :: "\n" :: (add_spaces rest)
  | "\n" :: rest -> "\n" :: (add_spaces rest)
  | x :: rest -> x :: " " :: (add_spaces rest)

let reparse tokens =
  let (env,code) = find_metavariables tokens in
  let env = String.concat "" (List.map snd env) in
  let code = find_when_dots code in
  let code = add_stars code in
  let code = String.concat "" code in
  let res = "@@\n"^env^"@@\n"^code in
  Printf.printf "%s\n\n" res;
  let out = Common.new_temp_file "sp" ".cocci" in
  let o = open_out out in
  Printf.fprintf o "%s\n" res;
  close_out o;
  out

let tokenize first =
  let lexbuf = Lexing.from_string first in
  let rec loop b =
    let tok = Lexer_cli.token b in
    if not (tok = Lexer_cli.EOF) then
      let s = Lexer_cli.pretty_print tok in
      s :: loop b
    else
      []
  in loop lexbuf

(* ---------------------------------------------------------------------- *)
(* entry point *)

let command_line args =
  let info =
    try Some (Common.split_when (function x -> List.mem x ["-sp";"--sp"]) args)
    with Not_found -> None in
  match info with
    None -> args
  | Some(pre_args,sp,post_args) ->
      (match post_args with
	first::post_args ->
	  pre_args @ "--sp-file" ::
		     (reparse (tokenize first)) ::
		     post_args
      | [] -> failwith "--sp needs an argument")
