flitter/lang_cpp/parsing/parse_cpp.ml
Joey Yakimowich-Payne c8efc2a088 Replace pfff with flitter
2018-06-04 13:03:46 +09:00

506 lines
17 KiB
OCaml

(* Yoann Padioleau
*
* Copyright (C) 2002-2013 Yoann Padioleau
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License (GPL)
* version 2 as published by the Free Software Foundation.
*
* This program 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
module Ast = Ast_cpp
module Flag = Flag_parsing_cpp
module PI = Parse_info
module Stat = Parse_info
module T = Parser_cpp
module TH = Token_helpers_cpp
module Lexer = Lexer_cpp
module Semantic = Parser_cpp_mly_helper
module Hack = Parsing_hacks_lib
module FT = File_type
(*****************************************************************************)
(* Prelude *)
(*****************************************************************************)
(*
* A heuristic based C/cpp/C++ parser.
*
* See "Parsing C/C++ Code without Pre-Preprocessing - Yoann Padioleau, CC'09"
* avalaible at http://padator.org/papers/yacfe-cc09.pdf
*)
(*****************************************************************************)
(* Types *)
(*****************************************************************************)
type toplevels_and_tokens = (Ast.toplevel * Parser_cpp.token list) list
let program_of_program2 xs =
xs +> List.map fst
exception Parse_error of Parse_info.info
(*****************************************************************************)
(* Wrappers *)
(*****************************************************************************)
let pr2, _pr2_once = Common2.mk_pr2_wrappers Flag_parsing_cpp.verbose_parsing
(*****************************************************************************)
(* Error diagnostic *)
(*****************************************************************************)
let error_msg_tok tok =
Parse_info.error_message_info (TH.info_of_tok tok)
(*****************************************************************************)
(* Stats on what was passed/commentized *)
(*****************************************************************************)
let commentized xs = xs +> Common.map_filter (function
| T.TComment_Pp (cppkind, ii) ->
if !Flag.filter_classic_passed
then
(match cppkind with
| Token_cpp.CppOther ->
let s = PI.str_of_info ii in
(match s with
| s when s =~ "KERN_.*" -> None
| s when s =~ "__.*" -> None
| _ -> Some (ii.PI.token)
)
| Token_cpp.CppDirective | Token_cpp.CppAttr | Token_cpp.CppMacro
-> None
| Token_cpp.CppMacroExpanded
| Token_cpp.CppPassingNormal
| Token_cpp.CppPassingCosWouldGetError
-> raise Todo
)
else Some (ii.PI.token)
| T.TAny_Action ii ->
Some (ii.PI.token)
| _ ->
None
)
let count_lines_commentized xs =
let line = ref (-1) in
let count = ref 0 in
commentized xs +> List.iter (function
| PI.OriginTok pinfo
| PI.ExpandedTok (_,pinfo,_) ->
let newline = pinfo.PI.line in
if newline <> !line
then begin
line := newline;
incr count
end
| _ -> ()
);
!count
(* See also problematic_lines and parsing_stat.ml *)
(* for most problematic tokens *)
let is_same_line_or_close line tok =
TH.line_of_tok tok =|= line ||
TH.line_of_tok tok =|= line - 1 ||
TH.line_of_tok tok =|= line - 2
(*****************************************************************************)
(* Lexing only *)
(*****************************************************************************)
(* called by parse below *)
let tokens2 file =
let table = Parse_info.full_charpos_to_pos_large file in
Common.with_open_infile file (fun chan ->
let lexbuf = Lexing.from_channel chan in
try
let rec tokens_aux () =
let tok = Lexer.token lexbuf in
(* fill in the line and col information *)
let tok = tok +> TH.visitor_info_of_tok (fun ii ->
{ ii with PI.token=
(* could assert pinfo.filename = file ? *)
match ii.PI.token with
| PI.OriginTok pi ->
PI.OriginTok (Parse_info.complete_token_location_large file
table pi)
| PI.ExpandedTok (pi,vpi, off) ->
PI.ExpandedTok(
(Parse_info.complete_token_location_large file table pi),vpi,
off)
| PI.FakeTokStr (s,vpi_opt) -> PI.FakeTokStr (s,vpi_opt)
| PI.Ab -> raise Impossible
})
in
if TH.is_eof tok
then [tok]
else tok::(tokens_aux ())
in
tokens_aux ()
with
| Lexer.Lexical s ->
failwith (spf "lexical error %s \n = %s"
s (PI.error_message file (PI.lexbuf_to_strpos lexbuf)))
| e -> raise e
)
let tokens a =
Common.profile_code "Parse_cpp.tokens" (fun () -> tokens2 a)
(*****************************************************************************)
(* Fuzzy parsing *)
(*****************************************************************************)
let rec multi_grouped_list xs =
xs +> List.map multi_grouped
and multi_grouped = function
| Token_views_cpp.Braces (tok1, xs, (Some tok2)) ->
Ast_fuzzy.Braces (tokext tok1, multi_grouped_list xs, tokext tok2)
| Token_views_cpp.Parens (tok1, xs, (Some tok2)) ->
Ast_fuzzy.Parens (tokext tok1, multi_grouped_list_comma xs, tokext tok2)
| Token_views_cpp.Angle (tok1, xs, (Some tok2)) ->
Ast_fuzzy.Angle (tokext tok1, multi_grouped_list xs, tokext tok2)
| Token_views_cpp.Tok (tok) ->
(match PI.str_of_info (tokext tok) with
| "..." -> Ast_fuzzy.Dots (tokext tok)
| s when Ast_fuzzy.is_metavar s -> Ast_fuzzy.Metavar (s, tokext tok)
| s -> Ast_fuzzy.Tok (s, tokext tok)
)
| _ -> failwith "could not find closing brace/parens/angle"
and tokext tok_extended =
TH.info_of_tok tok_extended.Token_views_cpp.t
and multi_grouped_list_comma xs =
let rec aux acc xs =
match xs with
| [] ->
if null acc
then []
else [Left (acc +> List.rev +> multi_grouped_list)]
| (x::xs) ->
(match x with
| Token_views_cpp.Tok tok when PI.str_of_info (tokext tok) = "," ->
let before = acc +> List.rev +> multi_grouped_list in
if null before
then aux [] xs
else (Left before)::(Right (tokext tok))::aux [] xs
| _ ->
aux (x::acc) xs
)
in
aux [] xs
(* This is similar to what I did for OPA. This is also similar
* to what I do for parsing hacks, but this fuzzy AST can be useful
* on its own, e.g. for a not too bad sgrep/spatch.
*
* note: this is similar to what cpplint/fblint of andrei does?
*)
let parse_fuzzy file =
Common.save_excursion Flag.sgrep_mode true (fun () ->
let toks_orig = tokens file in
let toks =
toks_orig +> Common.exclude (fun x ->
Token_helpers_cpp.is_comment x || Token_helpers_cpp.is_eof x
)
in
let extended = toks +> List.map Token_views_cpp.mk_token_extended in
Parsing_hacks_cpp.find_template_inf_sup extended;
let groups = Token_views_cpp.mk_multi extended in
multi_grouped_list groups, toks_orig
)
(*****************************************************************************)
(* Extract macros *)
(*****************************************************************************)
(* It can be used to to parse the macros defined in a macro.h file. It
* can also be used to try to extract the macros defined in the file
* that we try to parse *)
let extract_macros2 file =
Common.save_excursion Flag_parsing_cpp.verbose_lexing false (fun () ->
let toks = tokens (* todo: ~profile:false *) file in
let toks = Parsing_hacks_define.fix_tokens_define toks in
Pp_token.extract_macros toks
)
let extract_macros a =
Common.profile_code_exclusif "Parse_cpp.extract_macros" (fun () ->
extract_macros2 a)
(* less: pass it as a parameter to parse_program instead ?
* old: was a ref, but a hashtbl.t is actually already a kind of ref
*)
let (_defs : (string, Pp_token.define_body) Hashtbl.t) =
Hashtbl.create 101
(* We used to have also a init_defs_builtins() so that we could use a
* standard.h containing macros that were always useful, and a macros.h
* that the user could customize for his own project.
* But this was adding complexity so now we just have _defs and people
* can call add_defs to add local macro definitions.
*)
let add_defs file =
if not (Sys.file_exists file)
then failwith (spf "Could not find %s, have you set FLITTER_HOME correctly?"
file);
pr2 (spf "Using %s macro file" file);
let xs = extract_macros file in
xs +> List.iter (fun (k, v) -> Hashtbl.add _defs k v)
let init_defs file =
Hashtbl.clear _defs;
add_defs file
(*****************************************************************************)
(* Error recovery *)
(*****************************************************************************)
(* see parsing_recovery_cpp.ml *)
(*****************************************************************************)
(* Consistency checking *)
(*****************************************************************************)
(* todo: a parsing_consistency_cpp.ml *)
(*****************************************************************************)
(* Helper for main entry point *)
(*****************************************************************************)
(* Hacked lex. This function use refs passed by parse.
* 'tr' means 'token refs'. This is used mostly to enable
* error recovery (This used to do lots of stuff, such as
* calling some lookahead heuristics to reclassify
* tokens such as TIdent into TIdent_Typeded but this is
* now done in a fix_tokens style in parsing_hacks_typedef.ml.
*)
let rec lexer_function tr = fun lexbuf ->
match tr.PI.rest with
| [] -> (pr2 "LEXER: ALREADY AT END"; tr.PI.current)
| v::xs ->
tr.PI.rest <- xs;
tr.PI.current <- v;
tr.PI.passed <- v::tr.PI.passed;
if !Flag.debug_lexer then pr2_gen v;
if TH.is_comment v
then lexer_function (*~pass*) tr lexbuf
else v
(* was a define ? *)
let passed_a_define tr =
let xs = tr.PI.passed +> List.rev +> Common.exclude TH.is_comment in
if List.length xs >= 2
then
(match Common2.head_middle_tail xs with
| T.TDefine _, _, T.TCommentNewline_DefineEndOfMacro _ -> true
| _ -> false
)
else begin
pr2 "WIERD: length list of error recovery tokens < 2 ";
false
end
(*****************************************************************************)
(* Main entry point *)
(*****************************************************************************)
(*
* note: as now we go in two passes, there is first all the error message of
* the lexer, and then the error of the parser. It is not anymore
* interwinded.
*
* !!!This function use refs, and is not reentrant !!! so take care.
* It uses the _defs global defined above!!!!
*)
let parse_with_lang ?(lang=Flag_parsing_cpp.Cplusplus) file =
let stat = Parse_info.default_stat file in
let filelines = Common2.cat_array file in
(* -------------------------------------------------- *)
(* call lexer and get all the tokens *)
(* -------------------------------------------------- *)
let toks_orig = tokens file in
let toks =
try Parsing_hacks.fix_tokens ~macro_defs:_defs lang toks_orig
with Token_views_cpp.UnclosedSymbol s ->
pr2 s;
if !Flag.debug_cplusplus
then raise (Token_views_cpp.UnclosedSymbol s)
else toks_orig
in
let tr = Parse_info.mk_tokens_state toks in
let lexbuf_fake = Lexing.from_function (fun _buf _n -> raise Impossible) in
let rec loop () =
let info = TH.info_of_tok tr.PI.current in
(* todo?: I am not sure that it represents current_line, cos maybe
* tr.current partipated in the previous parsing phase, so maybe tr.current
* is not the first token of the next parsing phase. Same with checkpoint2.
* It would be better to record when we have a } or ; in parser.mly,
* cos we know that they are the last symbols of external_declaration2.
*)
let checkpoint = PI.line_of_info info in
(* bugfix: may not be equal to 'file' as after macro expansions we can
* start to parse a new entity from the body of a macro, for instance
* when parsing a define_machine() body, cf standard.h
*)
let checkpoint_file = PI.file_of_info info in
tr.PI.passed <- [];
(* for some statistics *)
let was_define = ref false in
let elem =
(try
(* -------------------------------------------------- *)
(* Call parser *)
(* -------------------------------------------------- *)
Parser_cpp.toplevel (lexer_function tr) lexbuf_fake
with e ->
if not !Flag.error_recovery
then raise (Parse_error (TH.info_of_tok tr.PI.current));
if !Flag.show_parsing_error then
(match e with
(* Lexical is not anymore launched I think *)
| Lexer.Lexical s ->
pr2 ("lexical error " ^s^ "\n =" ^ error_msg_tok tr.PI.current)
| Parsing.Parse_error ->
pr2 ("parse error \n = " ^ error_msg_tok tr.PI.current)
| Semantic.Semantic (s, _i) ->
pr2 ("semantic error " ^s^ "\n ="^ error_msg_tok tr.PI.current)
| e -> raise e
);
let line_error = TH.line_of_tok tr.PI.current in
let pbline =
tr.PI.passed
+> List.filter (is_same_line_or_close line_error)
+> List.filter TH.is_ident_like
in
let error_info =
(pbline +> List.map (fun tok->PI.str_of_info (TH.info_of_tok tok))),
line_error
in
stat.Stat.problematic_lines <-
error_info::stat.Stat.problematic_lines;
(* error recovery, go to next synchro point *)
let (passed', rest') =
Parsing_recovery_cpp.find_next_synchro tr.PI.rest tr.PI.passed in
tr.PI.rest <- rest';
tr.PI.passed <- passed';
tr.PI.current <- List.hd passed';
(* <> line_error *)
let info = TH.info_of_tok tr.PI.current in
let checkpoint2 = PI.line_of_info info in
let checkpoint2_file = PI.file_of_info info in
was_define := passed_a_define tr;
(if !was_define && !Flag.filter_define_error
then ()
else
(* bugfix: *)
(if (checkpoint_file = checkpoint2_file) && checkpoint_file = file
then PI.print_bad line_error (checkpoint, checkpoint2) filelines
else pr2 "PB: bad: but on tokens not from original file"
)
);
let info_of_bads =
Common2.map_eff_rev TH.info_of_tok tr.PI.passed in
Some (Ast.NotParsedCorrectly info_of_bads)
)
in
(* again not sure if checkpoint2 corresponds to end of bad region *)
let info = TH.info_of_tok tr.PI.current in
let checkpoint2 = PI.line_of_info info in
let checkpoint2_file = PI.file_of_info info in
let diffline =
if (checkpoint_file = checkpoint2_file) && (checkpoint_file = file)
then (checkpoint2 - checkpoint)
else 0
(* TODO? so if error come in middle of something ? where the
* start token was from original file but synchro found in body
* of macro ? then can have wrong number of lines stat.
* Maybe simpler just to look at tr.passed and count
* the lines in the token from the correct file ?
*)
in
let info = List.rev tr.PI.passed in
(* some stat updates *)
stat.Stat.commentized <-
stat.Stat.commentized + count_lines_commentized info;
(match elem with
| Some (Ast.NotParsedCorrectly _xs) ->
if !was_define && !Flag.filter_define_error
then stat.Stat.commentized <- stat.Stat.commentized + diffline
else stat.Stat.bad <- stat.Stat.bad + diffline
| _ -> stat.Stat.correct <- stat.Stat.correct + diffline
);
(match elem with
| None -> []
| Some xs -> (xs, info):: loop () (* recurse *)
)
in
let v = loop() in
(v, stat)
let parse2 file =
match File_type.file_type_of_file file with
| FT.PL (FT.C _) ->
(try
parse_with_lang ~lang:Flag.C file
with _exn ->
parse_with_lang ~lang:Flag.Cplusplus file
)
| FT.PL (FT.Cplusplus _) ->
parse_with_lang ~lang:Flag.Cplusplus file
| _ -> failwith (spf "not a C/C++ file: %s" file)
let parse file =
Common.profile_code "Parse_cpp.parse" (fun () ->
try
parse2 file
with Stack_overflow ->
pr2 (spf "PB stack overflow in %s" file);
[(Ast.NotParsedCorrectly [], ([]))], {Stat.
correct = 0;
bad = Common2.nblines_with_wc file;
filename = file;
have_timeout = true;
commentized = 0;
problematic_lines = [];
}
)
let parse_program file =
let (ast2, _stat) = parse file in
program_of_program2 ast2