Skip to content
Snippets Groups Projects
lexer.mll 9.5 KiB
Newer Older
  type release =
    | Current
    | Release of string * string
        (* codename, version *)
    | CurrentPlugin of string * string
	(* plugin-name, plugin-version *)
    | PluginRelease of string * string * string * string
	(* plugin-name, plugin-version, kernel-name, kernel-version *)
    | ToolRelease of string * string
	(* tool-name, tool-version *)

  type service =
    | Cmd
    | Main
    | Gui
    | Plugin of string * string option
    | Service of string

  type tag = Break | Bugfix | User | Developer | Commits

  type text =
    | Word of string
    | Quoted of string
    | PublicBts of int
    | PrivateBts of int
    | OldBts of int
Allan Blanchard's avatar
Allan Blanchard committed
    | Gitlab of bool * string * int
    | Item of char
    | Space
    | Newline

  type entry = {
    mutable service: service;
    mutable tags: tag list;
    mutable descr: text list;
  }

  let plugin_mode : string option ref = ref None
  let current_plugin = ref None
  let framac_services = [
    "ACSL";
    "Cil";
    "Configure";
    "Doc";
    "Journal";
    "Kernel";
    "Logic";
    "Lib";
    "Libc";
    "Makefile";
    "Project";
    "Ptests";
    "Typing"
  ]

  let releases : (release * entry list) list ref = ref [ Current, [] ]

  let file = ref "Changelog"
  let line = ref 1

  let error msg =
    Format.eprintf "%s:%d: " !file !line;
    Format.kfprintf
      (fun fmt ->
	 Format.pp_print_newline fmt () ;
	 exit 2)
      Format.err_formatter msg

  let set_release cp r =
    let entries = try List.assoc r !releases with Not_found -> [] in
    let others = List.filter (fun (r',_) -> r<>r') !releases in
    current_plugin := cp ;
    releases := (r, entries) :: others

  let add_entry e = function
    | ( r , es ) :: olders -> ( r , e::es ) :: olders
    | _ -> assert false

  let set_name e a =
    e.service <-
      match !plugin_mode , !current_plugin , a with
	| None , None , ("Cmd"|"CMD") -> Cmd
	| None , None , ("Gui"|"GUI") -> Gui
	| None , None , _ -> if List.mem a framac_services then Service a else Plugin(a,None)
	| None , Some p , ("Cmd"|"CMD") -> Plugin(p,Some "Cmd")
	| None , Some p , ("Gui"|"GUI") -> Plugin(p,Some "GUI")
	| None , Some p , s -> Plugin(p,Some s)
	| Some _ , None , s -> Service s
	| Some _ , Some p , s ->
	    if (String.uppercase_ascii p)=(String.uppercase_ascii s) then Main else Service a

  let set_tag e t = e.tags <- t :: e.tags

  let mk_entry mark =
    let entry = { service = Main; tags = []; descr = [] } in
    String.iter
      (function
	 | '-' -> set_tag entry User
	 | 'o' -> set_tag entry Developer
	 | '*' -> set_tag entry Bugfix
	 | '!' -> set_tag entry Break
	 | '+' -> set_tag entry Commits
	 | _ -> ())
      mark ;
    releases := add_entry entry !releases ;
    entry

  let add_word e w = match e.descr , w with
    | Space :: _ , Space -> ()
    | d , _ -> e.descr <- w :: d

}

let space   = [' ' '\t']
let newline = ['\n' '\r']+
let upper   = ['A'-'Z']
Augustin Lemesle's avatar
Augustin Lemesle committed
let letter  = ['a'-'z' 'A'-'Z' '_' '-']
let ident   = ['a'-'z' 'A'-'Z' '_' '0'-'9']
let file    = ['a'-'z' 'A'-'Z' '_' '-' '0'-'9']
let digit   = ['0'-'9']
let word    = [^ ' ' '\t' '\n' '\r']
let mark    = ['-' 'o' '*' '!' '+']

let vdigit = [ '0'-'9' '.' ]

(* ce is the current entry. None means no-entry at the beginning of line. *)
rule main ce = parse
  | (mark+) as mark    { entry (mk_entry mark) lexbuf }
  | '#' [^ '\n']* '\n' { incr line; main None lexbuf }
  | newline            { incr line; main None lexbuf }
  | eof                { }
  (* new Frama-C version name (major.minor (Element)) *)
  | space* (("Open Source"|"Binary") space+)+ "Release" space+
     ((digit+) '.' (digit+) ('.' (digit+))? as version) space+
     '(' ( (file+) as codename ) ')'
        set_release None (Release(codename,version)) ;
        main None lexbuf
      }
  | space* ("Plugin") space+ ((file+) as pname) space+
    ((digit+) '.' (digit+) ('.' (digit+))? as version) space+ '(' ( (file+) as codename ) ')'
      {
        set_release (Some pname) (Release(codename,version)) ;

  | space* ("Plugin") space+ ((file+) as pname) space+ '<'[^'>']+'>'
      {
        set_release (Some pname) (Current) ;
        main None lexbuf
      }

  | ( letter+ space+)* '<'[^'>']+'>'
    {
        set_release None (Current) ;
        main None lexbuf
    }
  | space
      {
	match ce with
	| None -> main ce lexbuf
	| Some e -> text e lexbuf
      }
  | _
      {
	error "Misformed entry (%S)" (Lexing.lexeme lexbuf)
      }

(* e is just created *)
and entry e = parse
  | space              { entry e lexbuf }
Augustin Lemesle's avatar
Augustin Lemesle committed
  | (letter+ space+ letter+) as a     { set_name e a ; text e lexbuf }
  | (letter+) as a     { set_name e a ; text e lexbuf }
  | _ { error "Missing plugin or service name (at %S)" (Lexing.lexeme lexbuf) }

(* text of entry *)
and text e = parse
  | '[' digit + '-' digit+ ('-' digit+)? ']' { (* date *) text e lexbuf }
  | newline newline { incr line ; add_word e Newline ; main (Some e) lexbuf }
  | newline        { incr line ; main (Some e) lexbuf }
  | space          { add_word e Space ; text e lexbuf }
  | eof            { }

  (* | space+ (digit as k) ')' { add_word e (Item k) ; text e lexbuf } *)
  | "frama-c/" ((letter+) as sub) '#' ((digit+) as bug)
Allan Blanchard's avatar
Allan Blanchard committed
      { add_word e (Gitlab(false, sub, int_of_string bug)) ; text e lexbuf }
  | '#' ((digit+) as bug)
      { add_word e (PublicBts(int_of_string bug)) ; text e lexbuf }

  | "#!" ((digit+) as bug)
      { add_word e (PrivateBts(int_of_string bug)) ; text e lexbuf }

  | "#?" ((digit+) as bug)
      { add_word e (OldBts(int_of_string bug)) ; text e lexbuf }

  | "#@" ((digit+) as bug)
Allan Blanchard's avatar
Allan Blanchard committed
      { add_word e (Gitlab(false, "frama-c", int_of_string bug)) ; text e lexbuf }

  | "##" ((digit+) as bug)
      { add_word e (Gitlab(true, "frama-c", int_of_string bug)) ; text e lexbuf }

  | '-' (letter | '-')+
  | '!'? (upper ident* '.')+ ident+
  | '~' ident+
  | '"' [^ '"' '\n']* '"'
  | ( (file+ | '.' | "..") '/' ) * file+ '.' file+
  | ( (file+ | '.' | "..") '/' ) + file+ '/'?
  | '\\' ident+
      { add_word e (Quoted(Lexing.lexeme lexbuf)) ; text e lexbuf }
  | letter+ ('-' letter+)*
      { add_word e (Word (Lexing.lexeme lexbuf)) ; text e lexbuf }

  | _ { add_word e (Word (Lexing.lexeme lexbuf)) ; text e lexbuf }

{

  let process filename =
    let cin = open_in filename in
    let lexbuf = Lexing.from_channel cin in
    file := filename ;
    line := 1 ;
    main None lexbuf

  let compare_string s1 s2 =
    let cmp = String.compare (String.uppercase_ascii s1) (String.uppercase_ascii s2) in
    if cmp = 0 then String.compare s1 s2 else cmp
  let rec compare_strings s1 s2 =
    match s1 , s2 with
      | [] , _ -> (-1)
      | _ , [] -> 1
      | x::s1 , y::s2 ->
	  let cmp = compare_string x y in
	  if cmp = 0 then compare_strings s1 s2 else cmp

  let service_compare s1 s2 =
    let rank = function
      | Main -> (0,[])
      | Cmd -> (1,[])
      | Gui -> (2,[])
      | Service a -> (3,[a])
      | Plugin(p,None) -> (4,[p])
      | Plugin(p,Some a) -> (4,[p;a])
    in
    let (r1,a1) = rank s1 in
    let (r2,a2) = rank s2 in
    if r1=r2 then compare_strings a1 a2 else r1-r2

  type version = {
    major : int;
    minor : int;
    patch : int;
  }

  let extract_version s =
    let re_version = Str.regexp "^\\([0-9]+\\)\\.\\([0-9]+\\)\\(\\.\\([0-9]+\\)\\)?$" in
    assert (Str.string_match re_version s 0);
    let major = int_of_string (Str.matched_group 1 s) in
    let minor = int_of_string (Str.matched_group 2 s) in
    let patch =
      try
        int_of_string (Str.matched_group 4 s)
      with _ -> 0
    in
    { major; minor; patch }

  (* compares both old-style (YYYYMMDD) and new-style numbering (MAJOR.MINOR)
     note: codenames are only used for old versions or in case of equality
   *)
  let compare_version v1 v2 cn1 cn2 =
    let re_old_numbering = Str.regexp ".*\\([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\\)" in
    let is_old_numbering v = Str.string_match re_old_numbering v 0 in
    let get_old_version v =
      let v = String.map (fun c -> if c = '_' then '-' else c) v in
      assert(Str.string_match re_old_numbering v 0) ;
      Str.matched_group 1 v
    in
    match is_old_numbering cn1, is_old_numbering cn2 with
    | true, true ->
      let r = String.compare (get_old_version cn2) (get_old_version cn1) in
      if r = 0 then String.compare v2 v1 else r
    | false, true -> (* new numbering is always newer *) -1
    | true, false -> (* old numbering is always older *) 1
    | false, false -> (* compare new-style versions *)
      let v1 = extract_version v1 in
      let v2 = extract_version v2 in
      let v = if v1.major = v2.major then if v1.minor = v2.minor then v2.patch - v1.patch
          else v2.minor - v1.minor
          else v2.major - v1.major
      in
      if v = 0 then (* same version number (1.0?); use lexicographic order *)
         String.compare cn2 cn1

  let compare_release (r1,_) (r2,_) =
    match r1 , r2 with
      | Current , Current -> 0
      | Current , _ -> -1
      | _ , Current -> 1
      | CurrentPlugin (_,v1), CurrentPlugin (_,v2) -> String.compare v2 v1
      | CurrentPlugin _, _ -> -1
      | _ , CurrentPlugin _-> 1
      | Release(cn1,v1) , Release(cn2,v2) -> compare_version v1 v2 cn1 cn2
      | Release _ , _ -> -1
      | _ , Release _ -> 1
      | PluginRelease(_,v1,_,a1) , PluginRelease(_,v2,_,a2) ->
	  String.compare (a2 ^ v2) (a1 ^ v1)
      | PluginRelease _ , _ -> -1
      | _, PluginRelease _ -> 1
      | ToolRelease(_,v1) , ToolRelease(_,v2) ->
	  String.compare v2 v1

  let releases () =
    List.map
      (fun (r,es) -> r , List.filter (fun e -> not (List.mem Commits e.tags)) es)
      (List.sort compare_release !releases)

}