992 lines
30 KiB
OCaml
992 lines
30 KiB
OCaml
(* Yoann Padioleau
|
|
*
|
|
* Copyright (C) 2010 Facebook
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* version 2.1 as published by the Free Software Foundation, with the
|
|
* special exception on linking described in file license.txt.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
|
|
* license.txt for more details.
|
|
*)
|
|
open Common
|
|
|
|
(*****************************************************************************)
|
|
(* Prelude *)
|
|
(*****************************************************************************)
|
|
(*
|
|
* Some helpers for the different lexers and parsers in flitter.
|
|
* The main types are:
|
|
* ('token_location' < 'token_origin' < 'token_mutable') * token_kind
|
|
*
|
|
*)
|
|
|
|
(*****************************************************************************)
|
|
(* Types *)
|
|
(*****************************************************************************)
|
|
|
|
(* Currently core/lexing.ml does not handle the line number position.
|
|
* Even if there are certain fields in the lexing structure, they are not
|
|
* maintained by the lexing engine so the following code does not work:
|
|
*
|
|
* let pos = Lexing.lexeme_end_p lexbuf in
|
|
* sprintf "at file %s, line %d, char %d" pos.pos_fname pos.pos_lnum
|
|
* (pos.pos_cnum - pos.pos_bol) in
|
|
*
|
|
* Hence those types and functions below to overcome the previous limitation,
|
|
* (see especially complete_token_location_large()).
|
|
*)
|
|
type token_location = {
|
|
str: string;
|
|
charpos: int;
|
|
|
|
line: int;
|
|
column: int;
|
|
|
|
file: filename;
|
|
}
|
|
(* with tarzan *)
|
|
|
|
let fake_token_location = {
|
|
charpos = -1; str = ""; line = -1; column = -1; file = "";
|
|
}
|
|
|
|
type token_origin =
|
|
(* Present both in the AST and list of tokens *)
|
|
| OriginTok of token_location
|
|
|
|
(* Present only in the AST and generated after parsing. Can be used
|
|
* when building some extra AST elements. *)
|
|
| FakeTokStr of string (* to help the generic pretty printer *) *
|
|
(* Sometimes we generate fake tokens close to existing
|
|
* origin tokens. This can be useful when have to give an error
|
|
* message that involves a fakeToken. The int is a kind of
|
|
* virtual position, an offset. See compare_pos below.
|
|
*)
|
|
(token_location * int) option
|
|
|
|
(* In the case of a XHP file, we could preprocess it and incorporate
|
|
* the tokens of the preprocessed code with the tokens from
|
|
* the original file. We want to mark those "expanded" tokens
|
|
* with a special tag so that if someone do some transformation on
|
|
* those expanded tokens they will get a warning (because we may have
|
|
* trouble back-propagating the transformation back to the original file).
|
|
*)
|
|
| ExpandedTok of
|
|
(* refers to the preprocessed file, e.g. /tmp/pp-xxxx.pphp *)
|
|
token_location *
|
|
(* kind of virtual position. This info refers to the last token
|
|
* before a serie of expanded tokens and the int is an offset.
|
|
* The goal is to be able to compare the position of tokens
|
|
* between then, even for expanded tokens. See compare_pos
|
|
* below.
|
|
*)
|
|
token_location * int
|
|
|
|
(* The Ab constructor is (ab)used to call '=' to compare
|
|
* big AST portions. Indeed as we keep the token information in the AST,
|
|
* if we have an expression in the code like "1+1" and want to test if
|
|
* it's equal to another code like "1+1" located elsewhere, then
|
|
* the Pervasives.'=' of OCaml will not return true because
|
|
* when it recursively goes down to compare the leaf of the AST, that is
|
|
* the token_location, there will be some differences of positions. If instead
|
|
* all leaves use Ab, then there is no position information and we can
|
|
* use '='. See also the 'al_info' function below.
|
|
*
|
|
* Ab means AbstractLineTok. I Use a short name to not
|
|
* polluate in debug mode.
|
|
*)
|
|
| Ab
|
|
|
|
(* with tarzan *)
|
|
|
|
type token_mutable = {
|
|
(* contains among other things the position of the token through
|
|
* the token_location embedded inside the token_origin type.
|
|
*)
|
|
token : token_origin;
|
|
mutable transfo: transformation;
|
|
(* less: mutable comments: ...; *)
|
|
}
|
|
|
|
(* poor's man refactoring *)
|
|
and transformation =
|
|
| NoTransfo
|
|
| Remove
|
|
| AddBefore of add
|
|
| AddAfter of add
|
|
| Replace of add
|
|
| AddArgsBefore of string list
|
|
|
|
and add =
|
|
| AddStr of string
|
|
| AddNewlineAndIdent
|
|
|
|
(* with tarzan *)
|
|
|
|
type token_kind =
|
|
(* for the fuzzy parser and sgrep/spatch fuzzy AST *)
|
|
| LPar
|
|
| RPar
|
|
| LBrace
|
|
| RBrace
|
|
(* for the unparser helpers in spatch, and to filter
|
|
* irrelevant tokens in the fuzzy parser
|
|
*)
|
|
| Esthet of esthet
|
|
(* mostly for the lexer helpers, and for fuzzy parser *)
|
|
(* less: want to factorize all those TH.is_eof to use that?
|
|
* but extra cost? same for TH.is_comment?
|
|
* todo: could maybe get rid of that now that we don't really use
|
|
* berkeley DB and prefer Prolog, and so we don't need a sentinel
|
|
* ast elements to associate the comments with it
|
|
*)
|
|
| Eof
|
|
|
|
| Other
|
|
|
|
and esthet =
|
|
| Comment
|
|
| Newline
|
|
| Space
|
|
|
|
(* shortcut *)
|
|
type info = token_mutable
|
|
|
|
|
|
type parsing_stat = {
|
|
filename: Common.filename;
|
|
mutable correct: int;
|
|
mutable bad: int;
|
|
(* used only for cpp for now *)
|
|
mutable have_timeout: bool;
|
|
(* by our cpp commentizer *)
|
|
mutable commentized: int;
|
|
(* if want to know exactly what was passed through, uncomment:
|
|
*
|
|
* mutable passing_through_lines: int;
|
|
*
|
|
* it differs from bad by starting from the error to
|
|
* the synchro point instead of starting from start of
|
|
* function to end of function.
|
|
*)
|
|
|
|
(* for instance to report most problematic macros when parse c/c++ *)
|
|
mutable problematic_lines:
|
|
(string list (* ident in error line *) * int (* line_error *)) list;
|
|
}
|
|
let default_stat file = {
|
|
filename = file;
|
|
have_timeout = false;
|
|
correct = 0; bad = 0;
|
|
commentized = 0;
|
|
problematic_lines = [];
|
|
}
|
|
|
|
|
|
(* Many parsers need to interact with the lexer, or use tricks around
|
|
* the stream of tokens, or do some error recovery, or just need to
|
|
* pass certain tokens (like the comments token) which requires
|
|
* to have access to this stream of remaining tokens.
|
|
* The token_state type helps.
|
|
*)
|
|
type 'tok tokens_state = {
|
|
mutable rest: 'tok list;
|
|
mutable current: 'tok;
|
|
(* it's passed since last "checkpoint", not passed from the beginning *)
|
|
mutable passed: 'tok list;
|
|
(* if want to do some lalr(k) hacking ... cf yacfe.
|
|
* mutable passed_clean : 'tok list;
|
|
* mutable rest_clean : 'tok list;
|
|
*)
|
|
}
|
|
let mk_tokens_state toks = {
|
|
rest = toks;
|
|
current = (List.hd toks);
|
|
passed = [];
|
|
(* passed_clean = [];
|
|
* rest_clean = (toks +> List.filter TH.is_not_comment);
|
|
*)
|
|
}
|
|
|
|
(*****************************************************************************)
|
|
(* Lexer helpers *)
|
|
(*****************************************************************************)
|
|
|
|
let lexbuf_to_strpos lexbuf =
|
|
(Lexing.lexeme lexbuf, Lexing.lexeme_start lexbuf)
|
|
|
|
let tokinfo_str_pos str pos =
|
|
{
|
|
token = OriginTok {
|
|
charpos = pos;
|
|
str = str;
|
|
|
|
(* info filled in a post-lexing phase, see complete_token_location_large*)
|
|
line = -1;
|
|
column = -1;
|
|
file = "";
|
|
};
|
|
transfo = NoTransfo;
|
|
}
|
|
|
|
(*
|
|
val rewrap_token_location : token_location.token_location -> info -> info
|
|
let rewrap_token_location pi ii =
|
|
{ii with pinfo =
|
|
(match ii.pinfo with
|
|
| OriginTok _oldpi -> OriginTok pi
|
|
| FakeTokStr _ | Ab | ExpandedTok _ ->
|
|
failwith "rewrap_parseinfo: no OriginTok"
|
|
)
|
|
}
|
|
*)
|
|
let token_location_of_info ii =
|
|
match ii.token with
|
|
| OriginTok pinfo -> pinfo
|
|
(* TODO ? dangerous ? *)
|
|
| ExpandedTok (pinfo_pp, _pinfo_orig, _offset) -> pinfo_pp
|
|
| FakeTokStr (_, (Some (pi, _))) -> pi
|
|
|
|
| FakeTokStr (_, None)
|
|
| Ab
|
|
-> failwith "token_location_of_info: no OriginTok"
|
|
|
|
(* for error reporting *)
|
|
(*
|
|
let string_of_token_location x =
|
|
spf "%s at %s:%d:%d" x.str x.file x.line x.column
|
|
*)
|
|
let string_of_token_location x =
|
|
spf "%s:%d:%d" x.file x.line x.column
|
|
|
|
let string_of_info x =
|
|
string_of_token_location (token_location_of_info x)
|
|
|
|
let str_of_info ii = (token_location_of_info ii).str
|
|
let file_of_info ii = (token_location_of_info ii).file
|
|
let line_of_info ii = (token_location_of_info ii).line
|
|
let col_of_info ii = (token_location_of_info ii).column
|
|
|
|
(* todo: return a Real | Virt position ? *)
|
|
let pos_of_info ii = (token_location_of_info ii).charpos
|
|
|
|
let pinfo_of_info ii = ii.token
|
|
|
|
let is_origintok ii =
|
|
match ii.token with
|
|
| OriginTok _ -> true
|
|
| _ -> false
|
|
|
|
(*
|
|
let opos_of_info ii =
|
|
PI.get_orig_info (function x -> x.PI.charpos) ii
|
|
|
|
val pos_of_tok : Parser_cpp.token -> int
|
|
val str_of_tok : Parser_cpp.token -> string
|
|
val file_of_tok : Parser_cpp.token -> Common.filename
|
|
|
|
let pos_of_tok x = Ast.opos_of_info (info_of_tok x)
|
|
let str_of_tok x = Ast.str_of_info (info_of_tok x)
|
|
let file_of_tok x = Ast.file_of_info (info_of_tok x)
|
|
let pinfo_of_tok x = Ast.pinfo_of_info (info_of_tok x)
|
|
|
|
val is_origin : Parser_cpp.token -> bool
|
|
val is_expanded : Parser_cpp.token -> bool
|
|
val is_fake : Parser_cpp.token -> bool
|
|
val is_abstract : Parser_cpp.token -> bool
|
|
|
|
|
|
let is_origin x =
|
|
match pinfo_of_tok x with Parse_info.OriginTok _ -> true | _ -> false
|
|
let is_expanded x =
|
|
match pinfo_of_tok x with Parse_info.ExpandedTok _ -> true | _ -> false
|
|
let is_fake x =
|
|
match pinfo_of_tok x with Parse_info.FakeTokStr _ -> true | _ -> false
|
|
let is_abstract x =
|
|
match pinfo_of_tok x with Parse_info.Ab -> true | _ -> false
|
|
*)
|
|
|
|
(* info about the current location *)
|
|
(*
|
|
let get_pi = function
|
|
| OriginTok pi -> pi
|
|
| ExpandedTok (_,pi,_) -> pi
|
|
| FakeTokStr (_,(Some (pi,_))) -> pi
|
|
| FakeTokStr (_,None) ->
|
|
failwith "FakeTokStr None"
|
|
| Ab ->
|
|
failwith "Ab"
|
|
*)
|
|
|
|
(* original info *)
|
|
let get_original_token_location = function
|
|
| OriginTok pi -> pi
|
|
| ExpandedTok (pi,_, _) -> pi
|
|
| FakeTokStr (_,_) -> failwith "no position information"
|
|
| Ab -> failwith "Ab"
|
|
|
|
(* used by token_helpers *)
|
|
(*
|
|
let get_info f ii =
|
|
match ii.token with
|
|
| OriginTok pi -> f pi
|
|
| ExpandedTok (_,pi,_) -> f pi
|
|
| FakeTokStr (_,Some (pi,_)) -> f pi
|
|
| FakeTokStr (_,None) ->
|
|
failwith "FakeTokStr None"
|
|
| Ab ->
|
|
failwith "Ab"
|
|
*)
|
|
(*
|
|
let get_orig_info f ii =
|
|
match ii.token with
|
|
| OriginTok pi -> f pi
|
|
| ExpandedTok (pi,_, _) -> f pi
|
|
| FakeTokStr (_,Some (pi,_)) -> f pi
|
|
| FakeTokStr (_,None ) ->
|
|
failwith "FakeTokStr None"
|
|
| Ab ->
|
|
failwith "Ab"
|
|
*)
|
|
|
|
(* not used but used to be useful in coccinelle *)
|
|
type posrv =
|
|
| Real of token_location
|
|
| Virt of
|
|
token_location (* last real info before expanded tok *) *
|
|
int (* virtual offset *)
|
|
|
|
let compare_pos ii1 ii2 =
|
|
let get_pos = function
|
|
| OriginTok pi -> Real pi
|
|
(* todo? I have this for lang_php/
|
|
| FakeTokStr (s, Some (pi_orig, offset)) ->
|
|
Virt (pi_orig, offset)
|
|
*)
|
|
| FakeTokStr _
|
|
| Ab
|
|
-> failwith "get_pos: Ab or FakeTok"
|
|
| ExpandedTok (_pi_pp, pi_orig, offset) ->
|
|
Virt (pi_orig, offset)
|
|
in
|
|
let pos1 = get_pos (pinfo_of_info ii1) in
|
|
let pos2 = get_pos (pinfo_of_info ii2) in
|
|
match (pos1,pos2) with
|
|
| (Real p1, Real p2) ->
|
|
compare p1.charpos p2.charpos
|
|
| (Virt (p1,_), Real p2) ->
|
|
if (compare p1.charpos p2.charpos) =|= (-1)
|
|
then (-1)
|
|
else 1
|
|
| (Real p1, Virt (p2,_)) ->
|
|
if (compare p1.charpos p2.charpos) =|= 1
|
|
then 1
|
|
else (-1)
|
|
| (Virt (p1,o1), Virt (p2,o2)) ->
|
|
let poi1 = p1.charpos in
|
|
let poi2 = p2.charpos in
|
|
match compare poi1 poi2 with
|
|
| -1 -> -1
|
|
| 0 -> compare o1 o2
|
|
| 1 -> 1
|
|
| _ -> raise Impossible
|
|
|
|
|
|
let min_max_ii_by_pos xs =
|
|
match xs with
|
|
| [] -> failwith "empty list, max_min_ii_by_pos"
|
|
| [x] -> (x, x)
|
|
| x::xs ->
|
|
let pos_leq p1 p2 = (compare_pos p1 p2) =|= (-1) in
|
|
xs +> List.fold_left (fun (minii,maxii) e ->
|
|
let maxii' = if pos_leq maxii e then e else maxii in
|
|
let minii' = if pos_leq e minii then e else minii in
|
|
minii', maxii'
|
|
) (x,x)
|
|
|
|
|
|
(*
|
|
let mk_info_item2 ~info_of_tok toks =
|
|
let buf = Buffer.create 100 in
|
|
let s =
|
|
(* old: get_slice_file filename (line1, line2) *)
|
|
begin
|
|
toks +> List.iter (fun tok ->
|
|
let info = info_of_tok tok in
|
|
match info.token with
|
|
| OriginTok _
|
|
| ExpandedTok _ ->
|
|
Buffer.add_string buf (str_of_info info)
|
|
|
|
(* the virtual semicolon *)
|
|
| FakeTokStr _ ->
|
|
()
|
|
| Ab -> raise Impossible
|
|
);
|
|
Buffer.contents buf
|
|
end
|
|
in
|
|
(s, toks)
|
|
|
|
let mk_info_item_DEPRECATED ~info_of_tok a =
|
|
Common.profile_code "Parsing.mk_info_item"
|
|
(fun () -> mk_info_item2 ~info_of_tok a)
|
|
*)
|
|
|
|
|
|
|
|
(*
|
|
I used to have:
|
|
type program2 = toplevel2 list
|
|
(* the token list contains also the comment-tokens *)
|
|
and toplevel2 = Ast_php.toplevel * Parser_php.token list
|
|
type program_with_comments = program2
|
|
|
|
and a function below called distribute_info_items_toplevel that
|
|
would distribute the list of tokens to each toplevel entity.
|
|
This was when I was storing parts of AST in berkeley DB and when
|
|
I wanted to get some information about an entity (a function, a class)
|
|
I wanted to get the list also of tokens associated with that entity.
|
|
|
|
Now I just have
|
|
type program_and_tokens = Ast_php.program * Parser_php.token list
|
|
because I don't use berkeley DB. I use codegraph and an entity_finder
|
|
we just focus on use/def and does not store huge asts on disk.
|
|
|
|
|
|
let rec distribute_info_items_toplevel2 xs toks filename =
|
|
match xs with
|
|
| [] -> raise Impossible
|
|
| [Ast_php.FinalDef e] ->
|
|
(* assert (null toks) ??? no cos can have whitespace tokens *)
|
|
let info_item = toks in
|
|
[Ast_php.FinalDef e, info_item]
|
|
| ast::xs ->
|
|
|
|
(match ast with
|
|
| Ast_js.St (Ast_js.Nop None) ->
|
|
distribute_info_items_toplevel2 xs toks filename
|
|
| _ ->
|
|
|
|
|
|
let ii = Lib_parsing_php.ii_of_any (Ast.Toplevel ast) in
|
|
(* ugly: I use a fakeInfo for lambda f_name, so I have
|
|
* have to filter the abstract info here
|
|
*)
|
|
let ii = List.filter PI.is_origintok ii in
|
|
let (min, max) = PI.min_max_ii_by_pos ii in
|
|
|
|
let toks_before_max, toks_after =
|
|
(* on very huge file, this function was previously segmentation fault
|
|
* in native mode because span was not tail call
|
|
*)
|
|
Common.profile_code "spanning tokens" (fun () ->
|
|
toks +> Common2.span_tail_call (fun tok ->
|
|
match PI.compare_pos (TH.info_of_tok tok) max with
|
|
| -1 | 0 -> true
|
|
| 1 -> false
|
|
| _ -> raise Impossible
|
|
))
|
|
in
|
|
let info_item = toks_before_max in
|
|
(ast, info_item)::distribute_info_items_toplevel2 xs toks_after filename
|
|
|
|
let distribute_info_items_toplevel a b c =
|
|
Common.profile_code "distribute_info_items" (fun () ->
|
|
distribute_info_items_toplevel2 a b c
|
|
)
|
|
|
|
*)
|
|
|
|
let rewrap_str s ii =
|
|
{ii with token =
|
|
(match ii.token with
|
|
| OriginTok pi -> OriginTok { pi with str = s;}
|
|
| FakeTokStr (s, info) -> FakeTokStr (s, info)
|
|
| Ab -> Ab
|
|
| ExpandedTok _ ->
|
|
(* ExpandedTok ({ pi with Common.str = s;},vpi) *)
|
|
failwith "rewrap_str: ExpandedTok not allowed here"
|
|
)
|
|
}
|
|
|
|
let tok_add_s s ii =
|
|
rewrap_str ((str_of_info ii) ^ s) ii
|
|
|
|
(*****************************************************************************)
|
|
(* vtoken -> ocaml *)
|
|
(*****************************************************************************)
|
|
let vof_filename v = Ocaml.vof_string v
|
|
|
|
let vof_token_location {
|
|
str = v_str;
|
|
charpos = v_charpos;
|
|
line = v_line;
|
|
column = v_column;
|
|
file = v_file
|
|
} =
|
|
let bnds = [] in
|
|
let arg = vof_filename v_file in
|
|
let bnd = ("file", arg) in
|
|
let bnds = bnd :: bnds in
|
|
let arg = Ocaml.vof_int v_column in
|
|
let bnd = ("column", arg) in
|
|
let bnds = bnd :: bnds in
|
|
let arg = Ocaml.vof_int v_line in
|
|
let bnd = ("line", arg) in
|
|
let bnds = bnd :: bnds in
|
|
let arg = Ocaml.vof_int v_charpos in
|
|
let bnd = ("charpos", arg) in
|
|
let bnds = bnd :: bnds in
|
|
let arg = Ocaml.vof_string v_str in
|
|
let bnd = ("str", arg) in let bnds = bnd :: bnds in Ocaml.VDict bnds
|
|
|
|
|
|
let vof_token_origin =
|
|
function
|
|
| OriginTok v1 ->
|
|
let v1 = vof_token_location v1 in Ocaml.VSum (("OriginTok", [ v1 ]))
|
|
| FakeTokStr (v1, opt) ->
|
|
let v1 = Ocaml.vof_string v1 in
|
|
let opt = Ocaml.vof_option (fun (p1, i) ->
|
|
Ocaml.VTuple [vof_token_location p1; Ocaml.vof_int i]
|
|
) opt
|
|
in
|
|
Ocaml.VSum (("FakeTokStr", [ v1; opt ]))
|
|
| Ab -> Ocaml.VSum (("Ab", []))
|
|
| ExpandedTok (v1, v2, v3) ->
|
|
let v1 = vof_token_location v1 in
|
|
let v2 = vof_token_location v2 in
|
|
let v3 = Ocaml.vof_int v3 in
|
|
Ocaml.VSum (("ExpandedTok", [ v1; v2; v3 ]))
|
|
|
|
|
|
let rec vof_transformation =
|
|
function
|
|
| NoTransfo -> Ocaml.VSum (("NoTransfo", []))
|
|
| Remove -> Ocaml.VSum (("Remove", []))
|
|
| AddBefore v1 -> let v1 = vof_add v1 in Ocaml.VSum (("AddBefore", [ v1 ]))
|
|
| AddAfter v1 -> let v1 = vof_add v1 in Ocaml.VSum (("AddAfter", [ v1 ]))
|
|
| Replace v1 -> let v1 = vof_add v1 in Ocaml.VSum (("Replace", [ v1 ]))
|
|
| AddArgsBefore v1 -> let v1 = Ocaml.vof_list Ocaml.vof_string v1 in Ocaml.VSum
|
|
(("AddArgsBefore", [ v1 ]))
|
|
|
|
and vof_add =
|
|
function
|
|
| AddStr v1 ->
|
|
let v1 = Ocaml.vof_string v1 in Ocaml.VSum (("AddStr", [ v1 ]))
|
|
| AddNewlineAndIdent -> Ocaml.VSum (("AddNewlineAndIdent", []))
|
|
|
|
let vof_info
|
|
{ token = v_token; transfo = v_transfo } =
|
|
let bnds = [] in
|
|
let arg = vof_transformation v_transfo in
|
|
let bnd = ("transfo", arg) in
|
|
let bnds = bnd :: bnds in
|
|
let arg = vof_token_origin v_token in
|
|
let bnd = ("token", arg) in
|
|
let bnds = bnd :: bnds in
|
|
Ocaml.VDict bnds
|
|
|
|
|
|
(*****************************************************************************)
|
|
(* Error location report *)
|
|
(*****************************************************************************)
|
|
|
|
(* A changen is a stand-in for a file for the underlying code. We use
|
|
* channels in the underlying parsing code as this avoids loading
|
|
* potentially very large source files directly into memory before we
|
|
* even parse them, but this makes it difficult to parse small chunks of
|
|
* code. The changen works around this problem by providing a channel,
|
|
* size and source for underlying data. This allows us to wrap a string
|
|
* in a channel, or pass a file, depending on our needs.
|
|
*)
|
|
type changen = unit -> (in_channel * int * Common.filename)
|
|
|
|
(* Many functions in parse_php were implemented in terms of files and
|
|
* are now adapted to work in terms of changens. However, we wish to
|
|
* provide the original API to users. This wraps changen-based functions
|
|
* and makes them operate on filenames again.
|
|
*)
|
|
let file_wrap_changen : (changen -> 'a) -> (Common.filename -> 'a) = fun f ->
|
|
(fun file ->
|
|
f (fun () -> (open_in file, Common2.filesize file, file)))
|
|
|
|
|
|
(*
|
|
let full_charpos_to_pos_from_changen changen =
|
|
let (chan, chansize, _) = changen () in
|
|
|
|
let size = (chansize + 2) in
|
|
|
|
let arr = Array.create size (0,0) in
|
|
|
|
let charpos = ref 0 in
|
|
let line = ref 0 in
|
|
|
|
let rec full_charpos_to_pos_aux () =
|
|
try
|
|
let s = (input_line chan) in
|
|
incr line;
|
|
|
|
(* '... +1 do' cos input_line dont return the trailing \n *)
|
|
for i = 0 to (String.length s - 1) + 1 do
|
|
arr.(!charpos + i) <- (!line, i);
|
|
done;
|
|
charpos := !charpos + String.length s + 1;
|
|
full_charpos_to_pos_aux();
|
|
|
|
with End_of_file ->
|
|
for i = !charpos to Array.length arr - 1 do
|
|
arr.(i) <- (!line, 0);
|
|
done;
|
|
();
|
|
in
|
|
begin
|
|
full_charpos_to_pos_aux ();
|
|
close_in chan;
|
|
arr
|
|
end
|
|
|
|
let full_charpos_to_pos2 = file_wrap_changen full_charpos_to_pos_from_changen
|
|
|
|
let full_charpos_to_pos a =
|
|
profile_code "Common.full_charpos_to_pos" (fun () -> full_charpos_to_pos2 a)
|
|
*)
|
|
|
|
(*
|
|
let test_charpos file =
|
|
full_charpos_to_pos file +> Common2.dump +> pr2
|
|
*)
|
|
|
|
(*
|
|
let complete_token_location filename table x =
|
|
{ x with
|
|
file = filename;
|
|
line = fst (table.(x.charpos));
|
|
column = snd (table.(x.charpos));
|
|
}
|
|
*)
|
|
|
|
let full_charpos_to_pos_large_from_changen = fun changen ->
|
|
let (chan, chansize, _) = changen () in
|
|
|
|
let size = (chansize + 2) in
|
|
|
|
(* old: let arr = Array.create size (0,0) in *)
|
|
let arr1 = Bigarray.Array1.create
|
|
Bigarray.int Bigarray.c_layout size in
|
|
let arr2 = Bigarray.Array1.create
|
|
Bigarray.int Bigarray.c_layout size in
|
|
Bigarray.Array1.fill arr1 0;
|
|
Bigarray.Array1.fill arr2 0;
|
|
|
|
let charpos = ref 0 in
|
|
let line = ref 0 in
|
|
|
|
let full_charpos_to_pos_aux () =
|
|
try
|
|
while true do begin
|
|
let s = (input_line chan) in
|
|
incr line;
|
|
|
|
(* '... +1 do' cos input_line dont return the trailing \n *)
|
|
for i = 0 to (String.length s - 1) + 1 do
|
|
(* old: arr.(!charpos + i) <- (!line, i); *)
|
|
arr1.{!charpos + i} <- (!line);
|
|
arr2.{!charpos + i} <- i;
|
|
done;
|
|
charpos := !charpos + String.length s + 1;
|
|
end done
|
|
with End_of_file ->
|
|
for i = !charpos to (* old: Array.length arr *)
|
|
Bigarray.Array1.dim arr1 - 1 do
|
|
(* old: arr.(i) <- (!line, 0); *)
|
|
arr1.{i} <- !line;
|
|
arr2.{i} <- 0;
|
|
done;
|
|
();
|
|
in
|
|
begin
|
|
full_charpos_to_pos_aux ();
|
|
close_in chan;
|
|
(fun i -> arr1.{i}, arr2.{i})
|
|
end
|
|
|
|
let full_charpos_to_pos_large2 =
|
|
file_wrap_changen full_charpos_to_pos_large_from_changen
|
|
|
|
let full_charpos_to_pos_large a =
|
|
profile_code "Common.full_charpos_to_pos_large"
|
|
(fun () -> full_charpos_to_pos_large2 a)
|
|
|
|
let complete_token_location_large filename table x =
|
|
{ x with
|
|
file = filename;
|
|
line = fst (table (x.charpos));
|
|
column = snd (table (x.charpos));
|
|
}
|
|
|
|
(*---------------------------------------------------------------------------*)
|
|
(* return line x col x str_line from a charpos. This function is quite
|
|
* expensive so don't use it to get the line x col from every token in
|
|
* a file. Instead use full_charpos_to_pos.
|
|
*)
|
|
let (info_from_charpos2: int -> filename -> (int * int * string)) =
|
|
fun charpos filename ->
|
|
|
|
(* Currently lexing.ml does not handle the line number position.
|
|
* Even if there is some fields in the lexing structure, they are not
|
|
* maintained by the lexing engine :( So the following code does not work:
|
|
* let pos = Lexing.lexeme_end_p lexbuf in
|
|
* sprintf "at file %s, line %d, char %d" pos.pos_fname pos.pos_lnum
|
|
* (pos.pos_cnum - pos.pos_bol) in
|
|
* Hence this function to overcome the previous limitation.
|
|
*)
|
|
let chan = open_in filename in
|
|
let linen = ref 0 in
|
|
let posl = ref 0 in
|
|
let rec charpos_to_pos_aux last_valid =
|
|
let s =
|
|
try Some (input_line chan)
|
|
with End_of_file when charpos =|= last_valid -> None in
|
|
incr linen;
|
|
match s with
|
|
Some s ->
|
|
let s = s ^ "\n" in
|
|
if (!posl + String.length s > charpos)
|
|
then begin
|
|
close_in chan;
|
|
(!linen, charpos - !posl, s)
|
|
end
|
|
else begin
|
|
posl := !posl + String.length s;
|
|
charpos_to_pos_aux !posl;
|
|
end
|
|
| None -> (!linen, charpos - !posl, "\n")
|
|
in
|
|
let res = charpos_to_pos_aux 0 in
|
|
close_in chan;
|
|
res
|
|
|
|
let info_from_charpos a b =
|
|
profile_code "Common.info_from_charpos" (fun () -> info_from_charpos2 a b)
|
|
|
|
|
|
(* Decalage is here to handle stuff such as cpp which include file and who
|
|
* can make shift.
|
|
*)
|
|
let (error_messagebis: filename -> (string * int) -> int -> string)=
|
|
fun filename (lexeme, lexstart) decalage ->
|
|
|
|
let charpos = lexstart + decalage in
|
|
let tok = lexeme in
|
|
let (line, pos, linecontent) = info_from_charpos charpos filename in
|
|
spf "File \"%s\", line %d, column %d, charpos = %d
|
|
around = '%s', whole content = %s"
|
|
filename line pos charpos tok (Common2.chop linecontent)
|
|
|
|
let error_message = fun filename (lexeme, lexstart) ->
|
|
try error_messagebis filename (lexeme, lexstart) 0
|
|
with
|
|
End_of_file ->
|
|
("PB in Common.error_message, position " ^ i_to_s lexstart ^
|
|
" given out of file:" ^ filename)
|
|
|
|
let error_message_token_location = fun info ->
|
|
let filename = info.file in
|
|
let lexeme = info.str in
|
|
let lexstart = info.charpos in
|
|
try error_messagebis filename (lexeme, lexstart) 0
|
|
with
|
|
End_of_file ->
|
|
("PB in Common.error_message, position " ^ i_to_s lexstart ^
|
|
" given out of file:" ^ filename)
|
|
|
|
let error_message_info info =
|
|
let pinfo = token_location_of_info info in
|
|
error_message_token_location pinfo
|
|
|
|
|
|
(*
|
|
let error_message_short = fun filename (lexeme, lexstart) ->
|
|
try
|
|
let charpos = lexstart in
|
|
let (line, pos, linecontent) = info_from_charpos charpos filename in
|
|
spf "File \"%s\", line %d" filename line
|
|
|
|
with End_of_file ->
|
|
begin
|
|
("PB in Common.error_message, position " ^ i_to_s lexstart ^
|
|
" given out of file:" ^ filename);
|
|
end
|
|
*)
|
|
|
|
let print_bad line_error (start_line, end_line) filelines =
|
|
begin
|
|
pr2 ("badcount: " ^ i_to_s (end_line - start_line));
|
|
|
|
for i = start_line to end_line do
|
|
let line = filelines.(i) in
|
|
|
|
if i =|= line_error
|
|
then pr2 ("BAD:!!!!!" ^ " " ^ line)
|
|
else pr2 ("bad:" ^ " " ^ line)
|
|
done
|
|
end
|
|
|
|
|
|
(*****************************************************************************)
|
|
(* Parsing statistics *)
|
|
(*****************************************************************************)
|
|
|
|
(* todo: stat per dir ? give in terms of func_or_decl numbers:
|
|
* nbfunc_or_decl pbs / nbfunc_or_decl total ?/
|
|
*
|
|
* note: cela dit si y'a des fichiers avec des #ifdef dont on connait pas les
|
|
* valeurs alors on parsera correctement tout le fichier et pourtant y'aura
|
|
* aucune def et donc aucune couverture en fait.
|
|
* ==> TODO evaluer les parties non parsé ?
|
|
*)
|
|
|
|
let print_parsing_stat_list ?(verbose=false)statxs =
|
|
(* old:
|
|
let total = List.length statxs in
|
|
let perfect =
|
|
statxs
|
|
+> List.filter (function
|
|
| {bad = n; _} when n = 0 -> true
|
|
| _ -> false)
|
|
+> List.length
|
|
in
|
|
|
|
pr2 "\n\n\n---------------------------------------------------------------";
|
|
pr2 (
|
|
(spf "NB total files = %d; " total) ^
|
|
(spf "perfect = %d; " perfect) ^
|
|
(spf "=========> %d" ((100 * perfect) / total)) ^ "%"
|
|
);
|
|
|
|
let good = statxs +> List.fold_left (fun acc {correct = x; _} -> acc+x) 0 in
|
|
let bad = statxs +> List.fold_left (fun acc {bad = x; _} -> acc+x) 0 in
|
|
|
|
let gf, badf = float_of_int good, float_of_int bad in
|
|
pr2 (
|
|
(spf "nb good = %d, nb bad = %d " good bad) ^
|
|
(spf "=========> %f" (100.0 *. (gf /. (gf +. badf))) ^ "%"
|
|
)
|
|
)
|
|
*)
|
|
let total = (List.length statxs) in
|
|
let perfect =
|
|
statxs
|
|
+> List.filter (function
|
|
{have_timeout = false; bad = 0; _} -> true | _ -> false)
|
|
+> List.length
|
|
in
|
|
|
|
if verbose then begin
|
|
pr "\n\n\n---------------------------------------------------------------";
|
|
pr "pbs with files:";
|
|
statxs
|
|
+> List.filter (function
|
|
| {have_timeout = true; _} -> true
|
|
| {bad = n; _} when n > 0 -> true
|
|
| _ -> false)
|
|
+> List.iter (function
|
|
{filename = file; have_timeout = timeout; bad = n; _} ->
|
|
pr (file ^ " " ^ (if timeout then "TIMEOUT" else i_to_s n));
|
|
);
|
|
|
|
pr "\n\n\n";
|
|
pr "files with lots of tokens passed/commentized:";
|
|
let threshold_passed = 100 in
|
|
statxs
|
|
+> List.filter (function
|
|
| {commentized = n; _} when n > threshold_passed -> true
|
|
| _ -> false)
|
|
+> List.iter (function
|
|
{filename = file; commentized = n; _} ->
|
|
pr (file ^ " " ^ (i_to_s n));
|
|
);
|
|
|
|
pr "\n\n\n";
|
|
end;
|
|
|
|
let good = statxs +> List.fold_left (fun acc {correct = x; _} -> acc+x) 0 in
|
|
let bad = statxs +> List.fold_left (fun acc {bad = x; _} -> acc+x) 0 in
|
|
let passed = statxs +> List.fold_left (fun acc {commentized = x; _} -> acc+x) 0
|
|
in
|
|
let total_lines = good + bad in
|
|
|
|
pr "---------------------------------------------------------------";
|
|
pr (
|
|
(spf "NB total files = %d; " total) ^
|
|
(spf "NB total lines = %d; " total_lines) ^
|
|
(spf "perfect = %d; " perfect) ^
|
|
(spf "pbs = %d; " (statxs +> List.filter (function
|
|
{bad = n; _} when n > 0 -> true | _ -> false)
|
|
+> List.length)) ^
|
|
(spf "timeout = %d; " (statxs +> List.filter (function
|
|
{have_timeout = true; _} -> true | _ -> false)
|
|
+> List.length)) ^
|
|
(spf "=========> %d" ((100 * perfect) / total)) ^ "%"
|
|
|
|
);
|
|
let gf, badf = float_of_int good, float_of_int bad in
|
|
let passedf = float_of_int passed in
|
|
pr (
|
|
(spf "nb good = %d, nb passed = %d " good passed) ^
|
|
(spf "=========> %f" (100.0 *. (passedf /. gf)) ^ "%")
|
|
);
|
|
pr (
|
|
(spf "nb good = %d, nb bad = %d " good bad) ^
|
|
(spf "=========> %f" (100.0 *. (gf /. (gf +. badf))) ^ "%"
|
|
)
|
|
)
|
|
|
|
(*****************************************************************************)
|
|
(* Most problematic tokens *)
|
|
(*****************************************************************************)
|
|
|
|
(* inspired by a comment by a reviewer of my CC'09 paper *)
|
|
let lines_around_error_line ~context (file, line) =
|
|
let arr = Common2.cat_array file in
|
|
|
|
let startl = max 0 (line - context) in
|
|
let endl = min (Array.length arr) (line + context) in
|
|
let res = ref [] in
|
|
|
|
for i = startl to endl -1 do
|
|
Common.push arr.(i) res
|
|
done;
|
|
List.rev !res
|
|
|
|
let print_recurring_problematic_tokens xs =
|
|
let h = Hashtbl.create 101 in
|
|
xs +> List.iter (fun x ->
|
|
let file = x.filename in
|
|
x.problematic_lines +> List.iter (fun (xs, line_error) ->
|
|
xs +> List.iter (fun s ->
|
|
Common2.hupdate_default s
|
|
(fun (old, example) -> old + 1, example)
|
|
(fun() -> 0, (file, line_error)) h;
|
|
)));
|
|
Common2.pr2_xxxxxxxxxxxxxxxxx();
|
|
pr2 ("maybe 10 most problematic tokens");
|
|
Common2.pr2_xxxxxxxxxxxxxxxxx();
|
|
Common.hash_to_list h
|
|
+> List.sort (fun (_k1,(v1,_)) (_k2,(v2,_)) -> compare v2 v1)
|
|
+> Common.take_safe 10
|
|
+> List.iter (fun (k,(i, (file_ex, line_ex))) ->
|
|
pr2 (spf "%s: present in %d parsing errors" k i);
|
|
pr2 ("example: ");
|
|
let lines = lines_around_error_line ~context:2 (file_ex, line_ex) in
|
|
lines +> List.iter (fun s -> pr2 (" " ^ s));
|
|
);
|
|
Common2.pr2_xxxxxxxxxxxxxxxxx();
|
|
()
|