(* Yoann Padioleau * * Copyright (C) 2011, 2014 Facebook * Copyright (C) 2007, 2008 Ecole des Mines de Nantes * * 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 Flag = Flag_parsing_cpp module PI = Parse_info module TH = Token_helpers_cpp open Parser_cpp (*****************************************************************************) (* Prelude *) (*****************************************************************************) (* * This module makes it easier to write some fuzzy parsing heuristics * by offering different "views" over the same set of tokens. * * Normally I should not use ref/mutable in the token_extended type below * and instead have a set of functions taking a list of tokens and * returning a list of tokens. The problem is that to make easier some * functions, it is better to work on better representation, on "views" * over this list of tokens. But then modifying those views and get * back from those views to the original simple list of tokens is * tedious. One way is to maintain next to the view a list of "actions" * (I was using a hash storing the charpos of the token and associating * the action) but it is tedious too. Simpler to use mutable/ref. We * use the same idea that we use when working on the Ast. * * old: when I was using the list of "actions" next to the views, the hash * indexed by the charpos, there could have been some problems: * how my fake_pos interact with the way I tag and adjust token ? * because I base my tagging on the position of the token ! so sometimes * could tag another fakeInfo that should not be tagged ? * fortunately I don't use anymore this technique. *) (*****************************************************************************) (* Some debugging functions *) (*****************************************************************************) let pr2, _pr2_once = Common2.mk_pr2_wrappers Flag.verbose_parsing (*****************************************************************************) (* Types *) (*****************************************************************************) type token_extended = { (* chose 't' and not 'tok' to have a short name because we will write * lots of ocaml patterns around this ... so better to be short *) mutable t: Parser_cpp.token; (* In C++ we have functions inside classes, so need a stack of context *) mutable where: context list; (* less: need also a after ? *) mutable new_tokens_before : Parser_cpp.token list; (* line x col cache (more easily accessible) of the info in the token *) line: int; col : int; } (* The strategy to tag is mostly to look at the token(s) before the '{' *) and context = | InTopLevel | InClassStruct of string (* can be __anon__ *) | InEnum | InInitializer | InAssign | InParameter | InArgument (* TODO actually commented in token_view_context because of c++ *) | InFunction (* | InTemplateParam (* TODO *) *) (* InCondition ? InParenExpr ? *) (* x list list, because x list separated by ',' *) type paren_grouped = | Parenthised of paren_grouped list list * token_extended list | PToken of token_extended type brace_grouped = | Braceised of brace_grouped list list * token_extended * token_extended option | BToken of token_extended (* Far better data structure than doing hacks in the lexer or parser * because in lexer we don't know to which ifdef a endif is related * and so when we want to comment a ifdef, we don't know which endif * we must also comment. Especially true for the #if 0 which sometimes * have a #else part. * * x list list, because x list separated by #else or #elif *) type ifdef_grouped = | Ifdef of ifdef_grouped list list * token_extended list | Ifdefbool of bool * ifdef_grouped list list * token_extended list | NotIfdefLine of token_extended list type 'a line_grouped = Line of 'a list type body_function_grouped = | BodyFunction of token_extended list | NotBodyLine of token_extended list (* quite similar to ast_fuzzy.ml but with extended token *) type multi_grouped = | Braces of token_extended * multi_grouped list * token_extended option | Parens of token_extended * multi_grouped list * token_extended option | Angle of token_extended * multi_grouped list * token_extended option | Tok of token_extended (* with tarzan *) exception UnclosedSymbol of string (*****************************************************************************) (* Helpers *) (*****************************************************************************) let mk_token_extended x = let info = TH.info_of_tok x in let (line, col) = PI.line_of_info info, PI.col_of_info info in { t = x; line = line; col = col; (* we use List.hd at a few places, so convenient to have a sentinel *) where = [InTopLevel]; new_tokens_before = []; } let mk_token_fake x = { t = x; line = -1; col = -1; where = [InTopLevel]; new_tokens_before = []; } let rebuild_tokens_extented toks_ext = let _tokens = ref [] in toks_ext +> List.iter (fun tok -> tok.new_tokens_before +> List.iter (fun x -> push x _tokens); push tok.t _tokens ); let tokens = List.rev !_tokens in (tokens +> Common2.acc_map mk_token_extended) (*****************************************************************************) (* View builders *) (*****************************************************************************) (* ------------------------------------------------------------------------- *) (* Parens *) (* ------------------------------------------------------------------------- *) (* todo: synchro ! use more indentation * if paren not closed and same indentation level, certainly because * part of a mid-ifdef-expression. * * c++ext: TODO: need to handle templates here. * The parenthized view must not consider the ',' in expressions * like foo(lexical cast, ...) as a separator for the arguments * of foo(), otherwise we will get [lexical_castTInf_Template translation. *) let rec mk_parenthised xs = match xs with | [] -> [] | x::xs -> (match x.t with | xx when TH.is_opar xx -> let body, extras, xs = mk_parameters [x] [] xs in Parenthised (body,extras)::mk_parenthised xs | _ -> PToken x::mk_parenthised xs ) (* return the body of the parenthised expression and the rest of the tokens *) and mk_parameters extras acc_before_sep xs = match xs with | [] -> (* maybe because of #ifdef which "opens" '(' in 2 branches *) pr2 "PB: not found closing paren in fuzzy parsing"; [List.rev acc_before_sep], List.rev extras, [] | x::xs -> (match x.t with (* synchro *) | xx when TH.is_obrace xx && x.col = 0 -> pr2 "PB: found synchro point } in paren"; [List.rev acc_before_sep], List.rev (extras), (x::xs) | xx when TH.is_cpar xx -> [List.rev acc_before_sep], List.rev (x::extras), xs | xx when TH.is_opar xx -> let body, extrasnest, xs = mk_parameters [x] [] xs in mk_parameters extras (Parenthised (body,extrasnest)::acc_before_sep) xs | TComma _ -> let body, extras, xs = mk_parameters (x::extras) [] xs in (List.rev acc_before_sep)::body, extras, xs | _ -> mk_parameters extras (PToken x::acc_before_sep) xs ) (* ------------------------------------------------------------------------- *) (* Brace *) (* ------------------------------------------------------------------------- *) let rec mk_braceised xs = match xs with | [] -> [] | x::xs -> (match x.t with | xx when TH.is_obrace xx -> let body, endbrace, xs = mk_braceised_aux [] xs in Braceised (body, x, endbrace)::mk_braceised xs | xx when TH.is_cbrace xx -> pr2 "PB: found closing brace alone in fuzzy parsing"; BToken x::mk_braceised xs | _ -> BToken x::mk_braceised xs ) (* return the body of the parenthised expression and the rest of the tokens *) and mk_braceised_aux acc xs = match xs with | [] -> (* maybe because of #ifdef which "opens" '(' in 2 branches *) pr2 "PB: not found closing brace in fuzzy parsing"; [List.rev acc], None, [] | x::xs -> (match x.t with | xx when TH.is_cbrace xx -> [List.rev acc], Some x, xs | xx when TH.is_obrace xx -> let body, endbrace, xs = mk_braceised_aux [] xs in mk_braceised_aux (Braceised (body,x, endbrace)::acc) xs | _ -> mk_braceised_aux (BToken x::acc) xs ) (* ------------------------------------------------------------------------- *) (* Ifdefs *) (* ------------------------------------------------------------------------- *) let rec mk_ifdef xs = match xs with | [] -> [] | x::xs -> (match x.t with | TIfdef _ -> let body, extra, xs = mk_ifdef_parameters [x] [] xs in Ifdef (body, extra)::mk_ifdef xs | TIfdefBool (b,_) -> let body, extra, xs = mk_ifdef_parameters [x] [] xs in (* if not passing, then consider a #if 0 as an ordinary #ifdef *) if !Flag.if0_passing then Ifdefbool (b, body, extra)::mk_ifdef xs else Ifdef(body, extra)::mk_ifdef xs | TIfdefMisc (b,_) | TIfdefVersion (b,_) -> let body, extra, xs = mk_ifdef_parameters [x] [] xs in Ifdefbool (b, body, extra)::mk_ifdef xs | _ -> (* todo? can have some Ifdef in the line ? *) let line, xs = Common.span (fun y -> y.line = x.line) (x::xs) in NotIfdefLine line::mk_ifdef xs ) and mk_ifdef_parameters extras acc_before_sep xs = match xs with | [] -> (* Note that mk_ifdef is assuming that CPP instruction are alone * on their line. Because I do a span (fun x -> is_same_line ...) * I might take with me a #endif if this one is mixed on a line * with some "normal" tokens. *) pr2 "PB: not found closing ifdef in fuzzy parsing"; [List.rev acc_before_sep], List.rev extras, [] | x::xs -> (match x.t with | TEndif _ -> [List.rev acc_before_sep], List.rev (x::extras), xs | TIfdef _ -> let body, extrasnest, xs = mk_ifdef_parameters [x] [] xs in mk_ifdef_parameters extras (Ifdef (body, extrasnest)::acc_before_sep) xs | TIfdefBool (b,_) -> let body, extrasnest, xs = mk_ifdef_parameters [x] [] xs in if !Flag.if0_passing then mk_ifdef_parameters extras (Ifdefbool (b, body, extrasnest)::acc_before_sep) xs else mk_ifdef_parameters extras (Ifdef (body, extrasnest)::acc_before_sep) xs | TIfdefMisc (b,_) | TIfdefVersion (b,_) -> let body, extrasnest, xs = mk_ifdef_parameters [x] [] xs in mk_ifdef_parameters extras (Ifdefbool (b, body, extrasnest)::acc_before_sep) xs | TIfdefelse _ | TIfdefelif _ -> let body, extras, xs = mk_ifdef_parameters (x::extras) [] xs in (List.rev acc_before_sep)::body, extras, xs | _ -> let line, xs = Common.span (fun y -> y.line = x.line) (x::xs) in mk_ifdef_parameters extras (NotIfdefLine line::acc_before_sep) xs ) (* ------------------------------------------------------------------------- *) (* Lines (of parens) *) (* ------------------------------------------------------------------------- *) let line_of_paren = function | PToken x -> x.line | Parenthised (_xxs, info_parens) -> (match info_parens with | [] -> raise Impossible | x::_xs -> x.line ) (* old let rec span_line_paren line = function | [] -> [],[] | x::xs -> (match x with | PToken tok when TH.is_eof tok.t -> [], x::xs | _ -> if line_of_paren x = line then let (l1, l2) = span_line_paren line xs in (x::l1, l2) else ([], x::xs) ) let rec mk_line_parenthised xs = match xs with | [] -> [] | x::xs -> let line_no = line_of_paren x in let line, xs = span_line_paren line_no xs in Line (x::line)::mk_line_parenthised xs *) let line_range_of_paren = function | PToken x -> x.line, x.line | Parenthised (_xxs, info_parens) -> (match info_parens with | [] -> raise Impossible | x::xs -> let lines_no = (x::xs) +> List.map (fun x -> x.line) in Common2.minimum lines_no, Common2.maximum lines_no ) let rec span_line_paren_range (imin, imax) = function | [] -> [],[] | x::xs -> (match x with | PToken tok when TH.is_eof tok.t -> [], x::xs | _ -> if line_of_paren x >= imin && line_of_paren x <= imax then (* may need to extend *) let (_imin', imax') = line_range_of_paren x in let (l1, l2) = span_line_paren_range (imin, max imax imax') xs in (x::l1, l2) else ([], x::xs) ) let rec mk_line_parenthised xs = match xs with | [] -> [] | x::xs -> let line_range = line_range_of_paren x in let line, xs = span_line_paren_range line_range xs in Line (x::line)::mk_line_parenthised xs (* ------------------------------------------------------------------------- *) (* Function body *) (* ------------------------------------------------------------------------- *) let rec mk_body_function_grouped xs = match xs with | [] -> [] | x::xs -> (match x with | {t=TOBrace _; col = 0; _} -> let is_closing_brace = function | {t = TCBrace _; col = 0; _ } -> true | _ -> false in let body, xs = Common.span (fun x -> not (is_closing_brace x)) xs in (match xs with | ({t = TCBrace _; col = 0; _ })::xs -> BodyFunction body::mk_body_function_grouped xs | [] -> pr2 "PB:not found closing brace in fuzzy parsing"; [NotBodyLine body] | _ -> raise Impossible ) | _ -> let line, xs = Common.span (fun y -> y.line = x.line) (x::xs) in NotBodyLine line::mk_body_function_grouped xs ) (* ------------------------------------------------------------------------- *) (* Multi ('{', '(', '<') (could also do '[' ?) *) (* ------------------------------------------------------------------------- *) (* Assumes work on a list of tokens without comments, without ifdefs * (todo? and without #define?). * Used for typedef inference. Now also used for fuzzy parsing! * * todo? more fault tolerance, if col == 0 and { the reset! * less: could check that it's consistent with the indentation * *) let mk_multi xs = let rec consume x xs = match x with | {t=(*TOBrace ii*)tok;_} when TH.is_obrace tok -> let body, closing, rest = look_close_brace x [] xs in Braces (x, body, closing), rest | {t=(*TOPar ii*)tok;_} when TH.is_opar tok -> let body, closing, rest = look_close_paren x [] xs in Parens (x, body, closing), rest | {t=TInf_Template _ii;_} -> let body, closing, rest = look_close_template x [] xs in Angle (x, body, closing), rest | x -> Tok x, xs and aux xs = match xs with | [] -> [] | x::xs -> let x', xs' = consume x xs in x'::aux xs' and look_close_brace tok_start accbody xs = match xs with | [] -> raise (UnclosedSymbol (spf "PB look_close_brace (started at %d)" (TH.line_of_tok tok_start.t))) | x::xs -> (match x with | {t=TCBrace _ii;_} -> List.rev accbody, Some x, xs (* Many macros have unclosed '{'. An alternative * would be to work on a view where define has been filtered *) | {t=TCommentNewline_DefineEndOfMacro _ii;_} -> List.rev accbody, None, x::xs | _ -> let (x', xs') = consume x xs in look_close_brace tok_start (x'::accbody) xs' ) and look_close_paren tok_start accbody xs = match xs with | [] -> raise (UnclosedSymbol (spf "PB look_close_paren (started at %d)" (TH.line_of_tok tok_start.t))) | x::xs -> (match x with | {t=(*TCPar ii*)tok;_} when TH.is_cpar tok -> List.rev accbody, Some x, xs | _ -> let (x', xs') = consume x xs in look_close_paren tok_start (x'::accbody) xs' ) and look_close_template tok_start accbody xs = match xs with | [] -> raise (UnclosedSymbol (spf "PB look_close_template (started at %d)" (TH.line_of_tok tok_start.t))) | x::xs -> (match x with | {t=TSup_Template _ii;_} -> List.rev accbody, Some x, xs | _ -> let (x', xs') = consume x xs in look_close_template tok_start (x'::accbody) xs' ) in aux xs let split_comma xs = xs +> Common2.split_gen_when (function | Tok{t=TComma _;_}::xs -> Some xs | _ -> None ) (*****************************************************************************) (* View iterators *) (*****************************************************************************) let rec iter_token_paren f xs = xs +> List.iter (function | PToken tok -> f tok; | Parenthised (xxs, info_parens) -> info_parens +> List.iter f; xxs +> List.iter (fun xs -> iter_token_paren f xs) ) let rec iter_token_brace f xs = xs +> List.iter (function | BToken tok -> f tok; | Braceised (xxs, tok1, tok2opt) -> f tok1; do_option f tok2opt; xxs +> List.iter (fun xs -> iter_token_brace f xs) ) let rec iter_token_ifdef f xs = xs +> List.iter (function | NotIfdefLine xs -> xs +> List.iter f; | Ifdefbool (_, xxs, info_ifdef) | Ifdef (xxs, info_ifdef) -> info_ifdef +> List.iter f; xxs +> List.iter (iter_token_ifdef f) ) let rec iter_token_multi f xs = xs +> List.iter (function | Tok t -> f t | Braces (t1, xs, t2) | Parens (t1, xs, t2) | Angle (t1, xs, t2) -> f t1; iter_token_multi f xs; Common.do_option f t2 ) let tokens_of_paren xs = let g = ref [] in xs +> iter_token_paren (fun tok -> push tok g); List.rev !g let tokens_of_paren_ordered xs = let g = ref [] in let rec aux_tokens_ordered = function | PToken tok -> push tok g; | Parenthised (xxs, info_parens) -> let (opar, cpar, commas) = match info_parens with | opar::xs -> (match List.rev xs with | cpar::xs -> opar, cpar, List.rev xs | _ -> raise Impossible ) | _ -> raise Impossible in push opar g; aux_args (xxs,commas); push cpar g; and aux_args (xxs, commas) = match xxs, commas with | [], [] -> () | [xs], [] -> xs +> List.iter aux_tokens_ordered | xs::ys::xxs, comma::commas -> xs +> List.iter aux_tokens_ordered; push comma g; aux_args (ys::xxs, commas) | _ -> raise Impossible in xs +> List.iter aux_tokens_ordered; List.rev !g let tokens_of_multi_grouped xs = let res = ref [] in let add x = Common.push x res in let rec aux xs = xs +> List.iter (function | Tok t1 -> add t1 | Braces (t1, xs, t2) | Parens (t1, xs, t2) | Angle (t1, xs, t2) -> add t1; aux xs; Common.do_option add t2 ) in aux xs; List.rev !res (*****************************************************************************) (* vof *) (*****************************************************************************) let vof_context = function | InTopLevel -> Ocaml.VSum ("T", []) | InClassStruct _s -> Ocaml.VSum ("C", []) | InEnum -> Ocaml.VSum ("E", []) | InInitializer -> Ocaml.VSum ("I", []) | InAssign -> Ocaml.VSum ("=", []) | InParameter -> Ocaml.VSum ("P", []) | InArgument -> Ocaml.VSum ("A", []) | InFunction -> Ocaml.VSum ("F", []) (* | InTemplateParam -> Ocaml.VSum ("<>", []) *) let vof_token_extended t = let info = TH.info_of_tok t.t in let str = PI.str_of_info info in let xs = List.map vof_context t.where in Ocaml.VTuple [Ocaml.VString str; Ocaml.VList xs] let rec vof_multi_grouped = function | Braces ((v1, v2, v3)) -> let v1 = vof_token_extended v1 and v2 = Ocaml.vof_list vof_multi_grouped v2 and v3 = Ocaml.vof_option vof_token_extended v3 in Ocaml.VSum (("Braces", [ v1; v2; v3 ])) | Parens ((v1, v2, v3)) -> let v1 = vof_token_extended v1 and v2 = Ocaml.vof_list vof_multi_grouped v2 and v3 = Ocaml.vof_option vof_token_extended v3 in Ocaml.VSum (("Parens", [ v1; v2; v3 ])) | Angle ((v1, v2, v3)) -> let v1 = vof_token_extended v1 and v2 = Ocaml.vof_list vof_multi_grouped v2 and v3 = Ocaml.vof_option vof_token_extended v3 in Ocaml.VSum (("Angle", [ v1; v2; v3 ])) | Tok v1 -> let v1 = vof_token_extended v1 in Ocaml.VSum (("Tok", [ v1 ])) let vof_multi_grouped_list xs = let v = Ocaml.VList (xs +> List.map vof_multi_grouped) in v