diff options
Diffstat (limited to 'toplevel/metasyntax.ml')
-rw-r--r-- | toplevel/metasyntax.ml | 1428 |
1 files changed, 1428 insertions, 0 deletions
diff --git a/toplevel/metasyntax.ml b/toplevel/metasyntax.ml new file mode 100644 index 00000000..84bda0af --- /dev/null +++ b/toplevel/metasyntax.ml @@ -0,0 +1,1428 @@ +(************************************************************************) +(* v * The Coq Proof Assistant / The Coq Development Team *) +(* <O___,, * CNRS-Ecole Polytechnique-INRIA Futurs-Universite Paris Sud *) +(* \VV/ **************************************************************) +(* // * This file is distributed under the terms of the *) +(* * GNU Lesser General Public License Version 2.1 *) +(************************************************************************) + +(* $Id: metasyntax.ml,v 1.105.2.2 2004/07/16 19:31:49 herbelin Exp $ *) + +open Pp +open Util +open Names +open Topconstr +open Coqast +open Ast +open Ppextend +open Extend +open Esyntax +open Libobject +open Library +open Summary +open Constrintern +open Vernacexpr +open Pcoq +open Rawterm +open Libnames + +let interp_global_rawconstr_with_vars vars c = + interp_rawconstr_gen false Evd.empty (Global.env()) false (vars,[]) c + +(**********************************************************************) +(* Parsing via ast (used in Zsyntax) *) + +(* This updates default parsers for Grammar actions and Syntax *) +(* patterns by inserting globalization *) +(* Done here to get parsing/g_*.ml4 non dependent from kernel *) +let constr_to_ast a = + Termast.ast_of_rawconstr (interp_rawconstr Evd.empty (Global.env()) a) + +(* This installs default quotations parsers to escape the ast parser *) +(* "constr" is used by default in quotations found in the ast parser *) +let constr_parser_with_glob = Pcoq.map_entry constr_to_ast Constr.constr + +let _ = define_ast_quotation true "constr" constr_parser_with_glob + +(**********************************************************************) +(* Globalisation for constr_expr *) + +let globalize_ref vars ref = + match Constrintern.interp_reference (vars,[]) ref with + | RRef (loc,VarRef a) -> Ident (loc,a) + | RRef (loc,a) -> Qualid (loc,qualid_of_sp (Nametab.sp_of_global a)) + | RVar (loc,x) -> Ident (loc,x) + | _ -> anomaly "globalize_ref: not a reference" + +let globalize_ref_term vars ref = + match Constrintern.interp_reference (vars,[]) ref with + | RRef (loc,VarRef a) -> CRef (Ident (loc,a)) + | RRef (loc,a) -> CRef (Qualid (loc,qualid_of_sp (Nametab.sp_of_global a))) + | RVar (loc,x) -> CRef (Ident (loc,x)) + | c -> Constrextern.extern_rawconstr Idset.empty c + +let rec globalize_constr_expr vars = function + | CRef ref -> globalize_ref_term vars ref + | CAppExpl (_,(p,ref),l) -> + let f = + map_constr_expr_with_binders globalize_constr_expr + (fun x e -> e) vars + in + CAppExpl (dummy_loc,(p,globalize_ref vars ref), List.map f l) + | c -> + map_constr_expr_with_binders globalize_constr_expr (fun id e -> id::e) + vars c + +let without_translation f x = + let old = Options.do_translate () in + let oldv7 = !Options.v7 in + Options.make_translate false; + try let r = f x in Options.make_translate old; Options.v7:=oldv7; r + with e -> Options.make_translate old; Options.v7:=oldv7; raise e + +let _ = set_constr_globalizer + (fun vars e -> for_grammar (without_translation (globalize_constr_expr vars)) e) + +(**********************************************************************) +(** For old ast printer *) + +(* Pretty-printer state summary *) +let _ = + declare_summary "syntax" + { freeze_function = Esyntax.freeze; + unfreeze_function = Esyntax.unfreeze; + init_function = Esyntax.init; + survive_module = false; + survive_section = false } + +(* Pretty-printing objects = syntax_entry *) +let cache_syntax (_,ppobj) = Esyntax.add_ppobject ppobj + +let subst_syntax (_,subst,ppobj) = + Extend.subst_syntax_command Ast.subst_astpat subst ppobj + +let (inPPSyntax,outPPSyntax) = + declare_object {(default_object "PPSYNTAX") with + open_function = (fun i o -> if i=1 then cache_syntax o); + cache_function = cache_syntax; + subst_function = subst_syntax; + classify_function = (fun (_,o) -> Substitute o); + export_function = (fun x -> Some x) } + +(* Syntax extension functions (registered in the environnement) *) + +(* Checking the pretty-printing rules against free meta-variables. + * Note that object are checked before they are added in the environment. + * Syntax objects in compiled modules are not re-checked. *) + +let add_syntax_obj whatfor sel = +(* if not !Options.v7_only then*) + Lib.add_anonymous_leaf (inPPSyntax (interp_syntax_entry whatfor sel)) + +(* Tokens *) + +let cache_token (_,s) = Pcoq.lexer.Token.using ("", s) + +let (inToken, outToken) = + declare_object {(default_object "TOKEN") with + open_function = (fun i o -> if i=1 then cache_token o); + cache_function = cache_token; + subst_function = Libobject.ident_subst_function; + classify_function = (fun (_,o) -> Substitute o); + export_function = (fun x -> Some x)} + +let add_token_obj s = Lib.add_anonymous_leaf (inToken s) + +(**********************************************************************) +(* Grammars (especially Tactic Notation) *) + +let make_terminal_status = function + | VTerm s -> Some s + | VNonTerm _ -> None + +let qualified_nterm current_univ = function + | NtQual (univ, en) -> (univ, en) + | NtShort en -> (current_univ, en) + +let rec make_tags = function + | VTerm s :: l -> make_tags l + | VNonTerm (loc, nt, po) :: l -> + let (u,nt) = qualified_nterm "tactic" nt in + let (etyp, _) = Egrammar.interp_entry_name u nt in + etyp :: make_tags l + | [] -> [] + +let declare_pprule = function + (* Pretty-printing rules only for Grammar (Tactic Notation) *) + | Egrammar.TacticGrammar gl -> + let f (s,(s',l),tac) = + let pp = (make_tags l, (s',List.map make_terminal_status l)) in + Pptactic.declare_extra_tactic_pprule true s pp; + Pptactic.declare_extra_tactic_pprule false s pp in + List.iter f gl + | _ -> () + +let cache_grammar (_,a) = + Egrammar.extend_grammar a; + declare_pprule a + +let subst_grammar (_,subst,a) = + Egrammar.subst_all_grammar_command subst a + +let (inGrammar, outGrammar) = + declare_object {(default_object "GRAMMAR") with + open_function = (fun i o -> if i=1 then cache_grammar o); + cache_function = cache_grammar; + subst_function = subst_grammar; + classify_function = (fun (_,o) -> Substitute o); + export_function = (fun x -> Some x)} + +(**********************************************************************) +(* V7 Grammar *) + +open Genarg + +let check_entry_type (u,n) = + if u = "tactic" or u = "vernac" then error "tactic and vernac not supported"; + match entry_type (get_univ u) n with + | None -> Pcoq.create_entry_if_new (get_univ u) n ConstrArgType + | Some (ConstrArgType | IdentArgType | RefArgType) -> () + | _ -> error "Cannot arbitrarily extend non constr/ident/ref entries" + +let add_grammar_obj univ entryl = + let u = create_univ_if_new univ in + let g = interp_grammar_command univ check_entry_type entryl in + Lib.add_anonymous_leaf (inGrammar (Egrammar.Grammar g)) + +(**********************************************************************) +(* V8 Grammar *) + +(* Tactic notations *) + +let locate_tactic_body dir (s,p,e) = (s,p,(dir,e)) + +let add_tactic_grammar g = + let dir = Lib.cwd () in + let g = List.map (locate_tactic_body dir) g in + Lib.add_anonymous_leaf (inGrammar (Egrammar.TacticGrammar g)) + +(* Printing grammar entries *) + +let print_grammar univ entry = + if !Options.v7 then + let u = get_univ univ in + let typ = explicitize_entry (fst u) entry in + let te,_,_ = get_constr_entry false typ in + Gram.Entry.print te + else + match entry with + | "constr" | "operconstr" | "binder_constr" -> + msgnl (str "Entry constr is"); + Gram.Entry.print Pcoq.Constr.constr; + msgnl (str "and lconstr is"); + Gram.Entry.print Pcoq.Constr.lconstr; + msgnl (str "where binder_constr is"); + Gram.Entry.print Pcoq.Constr.binder_constr; + msgnl (str "and operconstr is"); + Gram.Entry.print Pcoq.Constr.operconstr; + | "pattern" -> + Gram.Entry.print Pcoq.Constr.pattern + | "tactic" -> + Gram.Entry.print Pcoq.Tactic.simple_tactic + | _ -> error "Unknown or unprintable grammar entry" + +(* Parse a format (every terminal starting with a letter or a single + quote (except a single quote alone) must be quoted) *) + +let parse_format (loc,str) = + let str = " "^str in + let l = String.length str in + let push_token a = function + | cur::l -> (a::cur)::l + | [] -> [[a]] in + let push_white n l = + if n = 0 then l else push_token (UnpTerminal (String.make n ' ')) l in + let close_box i b = function + | a::(_::_ as l) -> push_token (UnpBox (b,a)) l + | _ -> error "Non terminated box in format" in + let close_quotation i = + if i < String.length str & str.[i] = '\'' & (i+1 = l or str.[i+1] = ' ') + then i+1 + else error "Incorrectly terminated quoted expression" in + let rec spaces n i = + if i < String.length str & str.[i] = ' ' then spaces (n+1) (i+1) + else n in + let rec nonspaces quoted n i = + if i < String.length str & str.[i] <> ' ' then + if str.[i] = '\'' & quoted & + (i+1 >= String.length str or str.[i+1] = ' ') + then if n=0 then error "Empty quoted token" else n + else nonspaces quoted (n+1) (i+1) + else + if quoted then error "Spaces are not allowed in (quoted) symbols" + else n in + let rec parse_non_format i = + let n = nonspaces false 0 i in + push_token (UnpTerminal (String.sub str i n)) (parse_token (i+n)) + and parse_quoted n i = + if i < String.length str then match str.[i] with + (* Parse " // " *) + | '/' when i <= String.length str & str.[i+1] = '/' -> + (* We forget the useless n spaces... *) + push_token (UnpCut PpFnl) + (parse_token (close_quotation (i+2))) + (* Parse " .. / .. " *) + | '/' when i <= String.length str -> + let p = spaces 0 (i+1) in + push_token (UnpCut (PpBrk (n,p))) + (parse_token (close_quotation (i+p+1))) + | c -> + (* The spaces are real spaces *) + push_white n (match c with + | '[' -> + if i <= String.length str then match str.[i+1] with + (* Parse " [h .. ", *) + | 'h' when i+1 <= String.length str & str.[i+2] = 'v' -> + (parse_box (fun n -> PpHVB n) (i+3)) + (* Parse " [v .. ", *) + | 'v' -> + parse_box (fun n -> PpVB n) (i+2) + (* Parse " [ .. ", *) + | ' ' | '\'' -> + parse_box (fun n -> PpHOVB n) (i+1) + | _ -> error "\"v\", \"hv\", \" \" expected after \"[\" in format" + else error "\"v\", \"hv\" or \" \" expected after \"[\" in format" + (* Parse "]" *) + | ']' -> + ([] :: parse_token (close_quotation (i+1))) + (* Parse a non formatting token *) + | c -> + let n = nonspaces true 0 i in + push_token (UnpTerminal (String.sub str (i-1) (n+2))) + (parse_token (close_quotation (i+n)))) + else + if n = 0 then [] + else error "Ending spaces non part of a format annotation" + and parse_box box i = + let n = spaces 0 i in + close_box i (box n) (parse_token (close_quotation (i+n))) + and parse_token i = + let n = spaces 0 i in + let i = i+n in + if i < l then match str.[i] with + (* Parse a ' *) + | '\'' when i+1 >= String.length str or str.[i+1] = ' ' -> + push_white (n-1) (push_token (UnpTerminal "'") (parse_token (i+1))) + (* Parse the beginning of a quoted expression *) + | '\'' -> + parse_quoted (n-1) (i+1) + (* Otherwise *) + | _ -> + push_white (n-1) (parse_non_format i) + else push_white n [[]] + in + try + if str <> "" then match parse_token 0 with + | [l] -> l + | _ -> error "Box closed without being opened in format" + else + error "Empty format" + with e -> + Stdpp.raise_with_loc loc e + +(***********************) +(* Analysing notations *) + +open Symbols + +type symbol_token = WhiteSpace of int | String of string + +let split_notation_string str = + let push_token beg i l = + if beg = i then l else + let s = String.sub str beg (i - beg) in + String s :: l + in + let push_whitespace beg i l = + if beg = i then l else WhiteSpace (i-beg) :: l + in + let rec loop beg i = + if i < String.length str then + if str.[i] = ' ' then + push_token beg i (loop_on_whitespace (i+1) (i+1)) + else + loop beg (i+1) + else + push_token beg i [] + and loop_on_whitespace beg i = + if i < String.length str then + if str.[i] <> ' ' then + push_whitespace beg i (loop i (i+1)) + else + loop_on_whitespace beg (i+1) + else + push_whitespace beg i [] + in + loop 0 0 + +let unquote_notation_token s = + let n = String.length s in + if n > 2 & s.[0] = '\'' & s.[n-1] = '\'' then String.sub s 1 (n-2) else s + +let is_normal_token str = + try let _ = Lexer.check_ident str in true with Lexer.Error _ -> false + +(* To protect alphabetic tokens and quotes from being seen as variables *) +let quote_notation_token x = + let n = String.length x in + let norm = is_normal_token x in + if (n > 0 & norm) or (n > 2 & x.[0] = '\'') then "'"^x^"'" + else x + +let rec raw_analyse_notation_tokens = function + | [] -> [], [] + | String ".." :: sl -> + let (vars,l) = raw_analyse_notation_tokens sl in + (list_add_set ldots_var vars, NonTerminal ldots_var :: l) + | String x :: sl when is_normal_token x -> + Lexer.check_ident x; + let id = Names.id_of_string x in + let (vars,l) = raw_analyse_notation_tokens sl in + if List.mem id vars then + error ("Variable "^x^" occurs more than once"); + (id::vars, NonTerminal id :: l) + | String s :: sl -> + Lexer.check_keyword s; + let (vars,l) = raw_analyse_notation_tokens sl in + (vars, Terminal (unquote_notation_token s) :: l) + | WhiteSpace n :: sl -> + let (vars,l) = raw_analyse_notation_tokens sl in + (vars, Break n :: l) + +let rec find_pattern xl = function + | Break n as x :: l, Break n' :: l' when n=n' -> + find_pattern (x::xl) (l,l') + | Terminal s as x :: l, Terminal s' :: l' when s = s' -> + find_pattern (x::xl) (l,l') + | [NonTerminal x], NonTerminal x' :: l' -> + (x,x',xl),l' + | [NonTerminal _], Terminal s :: _ | Terminal s :: _, _ -> + error ("The token "^s^" occurs on one side of \"..\" but not on the other side") + | [NonTerminal _], Break s :: _ | Break s :: _, _ -> + error ("A break occurs on one side of \"..\" but not on the other side") + | ((SProdList _ | NonTerminal _) :: _ | []), _ -> + error ("The special symbol \"..\" must occur in a configuration of the form\n\"x symbs .. symbs y\"") + +let rec interp_list_parser hd = function + | [] -> [], List.rev hd + | NonTerminal id :: tl when id = ldots_var -> + let ((x,y,sl),tl') = find_pattern [] (hd,tl) in + let yl,tl'' = interp_list_parser [] tl' in + (* We remember the second copy of each recursive part variable to *) + (* remove it afterwards *) + y::yl, SProdList (x,sl) :: tl'' + | (Terminal _ | Break _) as s :: tl -> + if hd = [] then + let yl,tl' = interp_list_parser [] tl in + yl, s :: tl' + else + interp_list_parser (s::hd) tl + | NonTerminal _ as x :: tl -> + let yl,tl' = interp_list_parser [x] tl in + yl, List.rev_append hd tl' + | SProdList _ :: _ -> anomaly "Unexpected SProdList in interp_list_parser" + +let analyse_notation_tokens l = + let vars,l = raw_analyse_notation_tokens l in + let recvars,l = interp_list_parser [] l in + ((if recvars = [] then [] else ldots_var::recvars), vars, l) + +let remove_vars = List.fold_right List.remove_assoc + +(* Build the syntax and grammar rules *) + +type printing_precedence = int * parenRelation +type parsing_precedence = int option + +let prec_assoc = function + | Gramext.RightA -> (L,E) + | Gramext.LeftA -> (E,L) + | Gramext.NonA -> (L,L) + +(* For old ast printer *) +let meta_pattern m = Pmeta(m,Tany) + +type white_status = Juxtapose | Separate of int | NextIsTerminal + +let precedence_of_entry_type from = function + | ETConstr (NumLevel n,BorderProd (_,None)) -> n, Prec n + | ETConstr (NumLevel n,BorderProd (left,Some a)) -> + n, let (lp,rp) = prec_assoc a in if left then lp else rp + | ETConstr (NumLevel n,InternalProd) -> n, Prec n + | ETConstr (NextLevel,_) -> from, L + | ETOther ("constr","annot") -> 10, Prec 10 + | _ -> 0, E (* ?? *) + +(* Some breaking examples *) +(* "x = y" : "x /1 = y" (breaks before any symbol) *) +(* "x =S y" : "x /1 =S /1 y" (protect from confusion; each side for symmetry)*) +(* "+ {" : "+ {" may breaks reversibility without space but oth. not elegant *) +(* "x y" : "x spc y" *) +(* "{ x } + { y }" : "{ x } / + { y }" *) +(* "< x , y > { z , t }" : "< x , / y > / { z , / t }" *) + +let is_left_bracket s = + let l = String.length s in l <> 0 & + (s.[0] = '{' or s.[0] = '[' or s.[0] = '(') + +let is_right_bracket s = + let l = String.length s in l <> 0 & + (s.[l-1] = '}' or s.[l-1] = ']' or s.[l-1] = ')') + +let is_left_bracket_on_left s = + let l = String.length s in l <> 0 & s.[l-1] = '>' + +let is_right_bracket_on_right s = + let l = String.length s in l <> 0 & s.[0] = '<' + +let is_comma s = + let l = String.length s in l <> 0 & + (s.[0] = ',' or s.[0] = ';') + +let is_operator s = + let l = String.length s in l <> 0 & + (s.[0] = '+' or s.[0] = '*' or s.[0] = '=' or + s.[0] = '-' or s.[0] = '/' or s.[0] = '<' or s.[0] = '>' or + s.[0] = '@' or s.[0] = '\\' or s.[0] = '&' or s.[0] = '~') + +type previous_prod_status = NoBreak | CanBreak + +let rec is_non_terminal = function + | NonTerminal _ | SProdList _ -> true + | _ -> false + +let add_break n l = UNP_BRK (n,1) :: l + +(* For old ast printer *) +let make_hunks_ast symbols etyps from = + let rec make ws = function + | NonTerminal m :: prods -> + let _,lp = precedence_of_entry_type from (List.assoc m etyps) in + let u = PH (meta_pattern (string_of_id m), None, lp) in + if prods <> [] && is_non_terminal (List.hd prods) then + u :: add_break 1 (make CanBreak prods) + else + u :: make CanBreak prods + + | Terminal s :: prods when List.exists is_non_terminal prods -> + let protect = + is_letter s.[0] || + (is_non_terminal (List.hd prods) && + (is_letter (s.[String.length s -1])) || + (is_digit (s.[String.length s -1]))) in + if is_comma s || is_right_bracket s then + RO s :: add_break 0 (make NoBreak prods) + else if (is_operator s || is_left_bracket s) && ws = CanBreak then + add_break (if protect then 1 else 0) + (RO (if protect then s^" " else s) :: make CanBreak prods) + else + if protect then + (if ws = CanBreak then add_break 1 else (fun x -> x)) + (RO (s^" ") :: make CanBreak prods) + else + RO s :: make CanBreak prods + + | Terminal s :: prods -> + RO s :: make NoBreak prods + + | Break n :: prods -> + add_break n (make NoBreak prods) + + | SProdList _ :: _ -> + anomaly "Recursive notations not supported in old syntax" + + | [] -> [] + + in make NoBreak symbols + +let add_break n l = UnpCut (PpBrk(n,0)) :: l + +let make_hunks etyps symbols from = + let vars,typs = List.split etyps in + let rec make ws = function + | NonTerminal m :: prods -> + let i = list_index m vars in + let _,prec = precedence_of_entry_type from (List.nth typs (i-1)) in + let u = UnpMetaVar (i ,prec) in + if prods <> [] && is_non_terminal (List.hd prods) then + u :: add_break 1 (make CanBreak prods) + else + u :: make CanBreak prods + + | Terminal s :: prods when List.exists is_non_terminal prods -> + if is_comma s then + UnpTerminal s :: add_break 1 (make NoBreak prods) + else if is_right_bracket s then + UnpTerminal s :: add_break 0 (make NoBreak prods) + else if is_left_bracket s then + if ws = CanBreak then + add_break 1 (UnpTerminal s :: make CanBreak prods) + else + UnpTerminal s :: make CanBreak prods + else if is_operator s then + if ws = CanBreak then + UnpTerminal (" "^s) :: add_break 1 (make NoBreak prods) + else + UnpTerminal s :: add_break 1 (make NoBreak prods) + else if is_ident_tail s.[String.length s - 1] then + if ws = CanBreak then + add_break 1 (UnpTerminal (s^" ") :: make CanBreak prods) + else + UnpTerminal (s^" ") :: make CanBreak prods + else if ws = CanBreak then + add_break 1 (UnpTerminal (s^" ") :: make CanBreak prods) + else + UnpTerminal s :: make CanBreak prods + + | Terminal s :: prods -> + if is_right_bracket s then + UnpTerminal s ::make NoBreak prods + else if ws = CanBreak then + add_break 1 (UnpTerminal s :: make NoBreak prods) + else + UnpTerminal s :: make NoBreak prods + + | Break n :: prods -> + add_break n (make NoBreak prods) + + | SProdList (m,sl) :: prods -> + let i = list_index m vars in + let _,prec = precedence_of_entry_type from (List.nth typs (i-1)) in + (* We add NonTerminal for simulation but remove it afterwards *) + let _,sl' = list_sep_last (make NoBreak (sl@[NonTerminal m])) in + UnpListMetaVar (i,prec,sl') :: make CanBreak prods + + | [] -> [] + + in make NoBreak symbols + +let hunks_of_format (from,(vars,typs) as vt) symfmt = + let rec aux = function + | symbs, (UnpTerminal s' as u) :: fmt + when s' = String.make (String.length s') ' ' -> + let symbs, l = aux (symbs,fmt) in symbs, u :: l + | Terminal s :: symbs, (UnpTerminal s' as u) :: fmt + when s = unquote_notation_token s' -> + let symbs, l = aux (symbs,fmt) in symbs, UnpTerminal s :: l + | NonTerminal s :: symbs, UnpTerminal s' :: fmt when s = id_of_string s' -> + let i = list_index s vars in + let _,prec = precedence_of_entry_type from (List.nth typs (i-1)) in + let symbs, l = aux (symbs,fmt) in symbs, UnpMetaVar (i,prec) :: l + | symbs, UnpBox (a,b) :: fmt -> + let symbs', b' = aux (symbs,b) in + let symbs', l = aux (symbs',fmt) in + symbs', UnpBox (a,b') :: l + | symbs, (UnpCut _ as u) :: fmt -> + let symbs, l = aux (symbs,fmt) in symbs, u :: l + | symbs, [] -> symbs, [] + | _, _ -> error "The format does not match the notation" + in + match aux symfmt with + | [], l -> l + | _ -> error "The format does not match the notation" + +let string_of_prec (n,p) = + (string_of_int n)^(match p with E -> "E" | L -> "L" | _ -> "") + +let assoc_of_type n (_,typ) = precedence_of_entry_type n typ + +let string_of_assoc = function + | Some(Gramext.RightA) -> "RIGHTA" + | Some(Gramext.LeftA) | None -> "LEFTA" + | Some(Gramext.NonA) -> "NONA" + +let is_not_small_constr = function + ETConstr _ -> true + | ETOther("constr","binder_constr") -> true + | _ -> false + +let rec define_keywords = function + NonTerm(_,Some(_,e)) as n1 :: Term("IDENT",k) :: l + when not !Options.v7 && is_not_small_constr e -> + prerr_endline ("Defining '"^k^"' as keyword"); + Lexer.add_token("",k); + n1 :: Term("",k) :: define_keywords l + | n :: l -> n :: define_keywords l + | [] -> [] + +let define_keywords = function + Term("IDENT",k)::l when not !Options.v7 -> + prerr_endline ("Defining '"^k^"' as keyword"); + Lexer.add_token("",k); + Term("",k) :: define_keywords l + | l -> define_keywords l + +let make_production etyps symbols = + let prod = + List.fold_right + (fun t l -> match t with + | NonTerminal m -> + let typ = List.assoc m etyps in + NonTerm (typ, Some (m,typ)) :: l + | Terminal s -> + Term (Extend.terminal s) :: l + | Break _ -> + l + | SProdList (x,sl) -> + let sl = List.flatten + (List.map (function Terminal s -> [Extend.terminal s] + | Break _ -> [] + | _ -> anomaly "Found a non terminal token in recursive notation separator") sl) in + let y = match List.assoc x etyps with + | ETConstr x -> x + | _ -> + error "Component of recursive patterns in notation must be constr" in + let typ = ETConstrList (y,sl) in + NonTerm (typ, Some (x,typ)) :: l) + symbols [] in + define_keywords prod + +let rec find_symbols c_current c_next c_last = function + | [] -> [] + | NonTerminal id :: sl -> + let prec = if sl <> [] then c_current else c_last in + (id, prec) :: (find_symbols c_next c_next c_last sl) + | Terminal s :: sl -> find_symbols c_next c_next c_last sl + | Break n :: sl -> find_symbols c_current c_next c_last sl + | SProdList (x,_) :: sl' -> + (x,c_next)::(find_symbols c_next c_next c_last sl') + +let border = function + | (_,ETConstr(_,BorderProd (_,a))) :: _ -> a + | _ -> None + +let recompute_assoc typs = + match border typs, border (List.rev typs) with + | Some Gramext.LeftA, Some Gramext.RightA -> assert false + | Some Gramext.LeftA, _ -> Some Gramext.LeftA + | _, Some Gramext.RightA -> Some Gramext.RightA + | _ -> None + +let rec expand_squash = function + | Term ("","{") :: NonTerm (ETConstr _, n) :: Term ("","}") :: l -> + NonTerm (ETConstr (NextLevel,InternalProd),n) + :: expand_squash l + | a :: l -> a :: expand_squash l + | [] -> [] + +let make_grammar_rule n typs symbols ntn perm = + let assoc = recompute_assoc typs in + let prod = make_production typs symbols in + (n,assoc,ntn,prod, perm) + +(* For old ast printer *) +let metas_of sl = + List.fold_right + (fun it metatl -> match it with + | NonTerminal m -> m::metatl + | _ -> metatl) + sl [] + +(* For old ast printer *) +let make_pattern symbols ast = + let env = List.map (fun m -> (string_of_id m,ETast)) (metas_of symbols) in + fst (to_pat env ast) + +(* For old ast printer *) +let make_syntax_rule n name symbols typs ast ntn sc = + [{syn_id = name; + syn_prec = n; + syn_astpat = make_pattern symbols ast; + syn_hunks = + [UNP_SYMBOLIC(sc,ntn,UNP_BOX (PpHOVB 1,make_hunks_ast symbols typs n))]}] + +let make_pp_rule (n,typs,symbols,fmt) = + match fmt with + | None -> [UnpBox (PpHOVB 0, make_hunks typs symbols n)] + | Some fmt -> + [UnpBox (PpHOVB 0, + hunks_of_format (n,List.split typs) (symbols,parse_format fmt))] + +(**************************************************************************) +(* Syntax extenstion: common parsing/printing rules and no interpretation *) + +(* v7 and translator : prec is for v7 (None if V8Notation), prec8 is for v8 *) +(* v8 : prec is for v8, prec8 is the same *) + +let pr_arg_level from = function + | (n,L) when n=from -> str "at next level" + | (n,E) -> str "at level " ++ int n + | (n,L) -> str "at level below " ++ int n + | (n,Prec m) when m=n -> str "at level " ++ int n + | (n,_) -> str "Unknown level" + +let pr_level ntn (from,args) = + let lopen = ntn.[0] = '_' and ropen = ntn.[String.length ntn - 1] = '_' in +(* + let ppassoc, args = match args with + | [] -> mt (), [] + | (nl,lpr)::l when nl=from & fst (list_last l)=from -> + let (_,rpr),l = list_sep_last l in + match lpr, snd (list_last l) with + | L,E -> Gramext.RightA, l + | E,L -> Gramext.LeftA, l + | L,L -> Gramext.NoneA, l + | _ -> args +*) + str "at level " ++ int from ++ spc () ++ str "with arguments" ++ spc() ++ + prlist_with_sep pr_coma (pr_arg_level from) args + +let cache_syntax_extension (_,(_,(prec,prec8),ntn,gr,se)) = + try + let oldprec, oldprec8 = Symbols.level_of_notation ntn in + if prec8 <> oldprec8 & (Options.do_translate () or not !Options.v7) then + errorlabstrm "" + (str ((if Options.do_translate () then "For new syntax, notation " + else "Notation ") + ^ntn^" is already defined") ++ spc() ++ pr_level ntn oldprec8 ++ + spc() ++ str "while it is now required to be" ++ spc() ++ + pr_level ntn prec8) + else + (* Inconsistent v8 notations but not while translating; forget... *) + (); + (* V8 notations are consistent (from both translator or v8) *) + if prec <> None & !Options.v7 then begin + (* Update the V7 parsing rule *) + if oldprec <> None & out_some oldprec <> out_some prec then + (* None of them is V8Notation and they are different: warn *) + Options.if_verbose + warning ("Notation "^ntn^ + " was already assigned a different level or sublevels"); + if oldprec = None or out_some oldprec <> out_some prec then + Egrammar.extend_grammar (Egrammar.Notation (out_some gr)) + end + with Not_found -> + (* Reserve the notation level *) + Symbols.declare_notation_level ntn (prec,prec8); + (* Declare the parsing rule *) + option_iter (fun gr -> Egrammar.extend_grammar (Egrammar.Notation gr)) gr; + (* Declare the printing rule *) + Symbols.declare_notation_printing_rule ntn (se,fst prec8) + +let subst_notation_grammar subst x = x + +let subst_printing_rule subst x = x + +let subst_syntax_extension (_,subst,(local,prec,ntn,gr,se)) = + (local,prec,ntn, + option_app (subst_notation_grammar subst) gr, + subst_printing_rule subst se) + +let classify_syntax_definition (_,(local,_,_,_,_ as o)) = + if local then Dispose else Substitute o + +let export_syntax_definition (local,_,_,_,_ as o) = + if local then None else Some o + +let (inSyntaxExtension, outSyntaxExtension) = + declare_object {(default_object "SYNTAX-EXTENSION") with + open_function = (fun i o -> if i=1 then cache_syntax_extension o); + cache_function = cache_syntax_extension; + subst_function = subst_syntax_extension; + classify_function = classify_syntax_definition; + export_function = export_syntax_definition} + +let interp_modifiers = + let onlyparsing = ref false in + let rec interp assoc level etyps format = function + | [] -> + (assoc,level,etyps,!onlyparsing,format) + | SetEntryType (s,typ) :: l -> + let id = id_of_string s in + if List.mem_assoc id etyps then + error (s^" is already assigned to an entry or constr level") + else interp assoc level ((id,typ)::etyps) format l + | SetItemLevel ([],n) :: l -> + interp assoc level etyps format l + | SetItemLevel (s::idl,n) :: l -> + let id = id_of_string s in + if List.mem_assoc id etyps then + error (s^" is already assigned to an entry or constr level") + else + let typ = ETConstr (n,()) in + interp assoc level ((id,typ)::etyps) format (SetItemLevel (idl,n)::l) + | SetLevel n :: l -> + if level <> None then error "A level is given more than once" + else interp assoc (Some n) etyps format l + | SetAssoc a :: l -> + if assoc <> None then error "An associativity is given more than once" + else interp (Some a) level etyps format l + | SetOnlyParsing :: l -> + onlyparsing := true; + interp assoc level etyps format l + | SetFormat s :: l -> + if format <> None then error "A format is given more than once" + onlyparsing := true; + interp assoc level etyps (Some s) l + in interp None None [] None + +let merge_modifiers a n l = + (match a with None -> [] | Some a -> [SetAssoc a]) @ + (match n with None -> [] | Some n -> [SetLevel n]) @ l + +let interp_infix_modifiers modl = + let (assoc,level,t,b,fmt) = interp_modifiers modl in + if t <> [] then + error "explicit entry level or type unexpected in infix notation"; + (assoc,level,b,fmt) + +(* 2nd list of types has priority *) +let rec merge_entry_types etyps' = function + | [] -> etyps' + | (x,_ as e)::etyps -> + e :: merge_entry_types (List.remove_assoc x etyps') etyps + +let set_entry_type etyps (x,typ) = + let typ = try + match List.assoc x etyps, typ with + | ETConstr (n,()), (_,BorderProd (left,_)) -> + ETConstr (n,BorderProd (left,None)) + | ETConstr (n,()), (_,InternalProd) -> ETConstr (n,InternalProd) + | (ETPattern | ETIdent | ETBigint | ETOther _ | ETReference as t), _ -> t + | (ETConstrList _, _) -> assert false + with Not_found -> ETConstr typ + in (x,typ) + +let check_rule_reversibility l = + if List.for_all (function NonTerminal _ -> true | _ -> false) l then + error "A notation must include at least one symbol" + +let find_precedence_v7 lev etyps symbols = + (match symbols with + | NonTerminal x :: _ -> + (try match List.assoc x etyps with + | ETConstr _ -> + error "The level of the leftmost non-terminal cannot be changed" + | _ -> () + with Not_found -> ()) + | _ -> ()); + if lev = None then 1 else out_some lev + +let find_precedence lev etyps symbols = + match symbols with + | NonTerminal x :: _ -> + (try match List.assoc x etyps with + | ETConstr _ -> + error "The level of the leftmost non-terminal cannot be changed" + | ETIdent | ETBigint | ETReference -> + if lev = None then + Options.if_verbose msgnl (str "Setting notation at level 0") + else + if lev <> Some 0 then + error "A notation starting with an atomic expression must be at level 0"; + 0 + | ETPattern | ETOther _ -> (* Give a default ? *) + if lev = None then + error "Need an explicit level" + else out_some lev + | ETConstrList _ -> assert false (* internally used in grammar only *) + with Not_found -> + if lev = None then + error "A left-recursive notation must have an explicit level" + else out_some lev) + | Terminal _ ::l when + (match list_last symbols with Terminal _ -> true |_ -> false) + -> + if lev = None then + (Options.if_verbose msgnl (str "Setting notation at level 0"); 0) + else out_some lev + | _ -> + if lev = None then error "Cannot determine the level"; + out_some lev + +let check_curly_brackets_notation_exists () = + try let _ = Symbols.level_of_notation "{ _ }" in () + with Not_found -> + error "Notations involving patterns of the form \"{ _ }\" are treated \n\ +specially and require that the notation \"{ _ }\" is already reserved" + +(* Remove patterns of the form "{ _ }", unless it is the "{ _ }" notation *) +let remove_curly_brackets l = + let rec next = function + | Break _ :: l -> next l + | l -> l in + let rec aux deb = function + | [] -> [] + | Terminal "{" as t1 :: l -> + (match next l with + | NonTerminal _ as x :: l' as l0 -> + (match next l' with + | Terminal "}" as t2 :: l'' as l1 -> + if l <> l0 or l' <> l1 then + warning "Skipping spaces inside curly brackets"; + if deb & l'' = [] then [t1;x;t2] else begin + check_curly_brackets_notation_exists (); + x :: aux false l'' + end + | l1 -> t1 :: x :: aux false l1) + | l0 -> t1 :: aux false l0) + | x :: l -> x :: aux false l + in aux true l + +let compute_syntax_data forv7 (df,modifiers) = + let (assoc,n,etyps,onlyparse,fmt) = interp_modifiers modifiers in + (* Notation defaults to NONA *) + let assoc = match assoc with None -> Some Gramext.NonA | a -> a in + let toks = split_notation_string df in + let (recvars,vars,symbols) = analyse_notation_tokens toks in + let ntn_for_interp = make_notation_key symbols in + let symbols = remove_curly_brackets symbols in + let notation = make_notation_key symbols in + check_rule_reversibility symbols; + let n = + if !Options.v7 then find_precedence_v7 n etyps symbols + else find_precedence n etyps symbols in + let innerlevel = NumLevel (if forv7 then 10 else 200) in + let typs = + find_symbols + (NumLevel n,BorderProd(true,assoc)) + (innerlevel,InternalProd) + (NumLevel n,BorderProd(false,assoc)) + symbols in + (* To globalize... *) + let typs = List.map (set_entry_type etyps) typs in + let ppdata = (n,typs,symbols,fmt) in + let prec = (n,List.map (assoc_of_type n) typs) in + ((onlyparse,recvars,vars, + ntn_for_interp,notation),prec,ppdata,(Lib.library_dp(),df)) + +let add_syntax_extension local mv mv8 = + let data8 = option_app (compute_syntax_data false) mv8 in + let data = option_app (compute_syntax_data !Options.v7) mv in + let prec,gram_rule = match data with + | None -> None, None + | Some ((_,_,_,_,notation),prec,(n,typs,symbols,_),_) -> + Some prec, Some (make_grammar_rule n typs symbols notation None) in + match data, data8 with + | None, None -> (* Nothing to do: V8Notation while not translating *) () + | _, Some d | Some d, None -> + let ((_,_,_,_,ntn),ppprec,ppdata,_) = d in + let ntn' = match data with Some ((_,_,_,_,ntn),_,_,_) -> ntn | _ -> ntn in + let pp_rule = make_pp_rule ppdata in + Lib.add_anonymous_leaf + (inSyntaxExtension (local,(prec,ppprec),ntn',gram_rule,pp_rule)) + +(**********************************************************************) +(* Distfix, Infix, Symbols *) + +(* A notation comes with a grammar rule, a pretty-printing rule, an + identifiying pattern called notation and an associated scope *) +let load_notation _ (_,(_,_,ntn,scope,pat,onlyparse,_,_)) = + option_iter Symbols.declare_scope scope + +let open_notation i (_,(_,oldse,ntn,scope,pat,onlyparse,pp8only,df)) = + if i=1 then begin + let b,oldpp8only = Symbols.exists_notation_in_scope scope ntn pat in + (* Declare the old printer rule and its interpretation *) + if (not b or oldpp8only) & oldse <> None then + Esyntax.add_ppobject {sc_univ="constr";sc_entries=out_some oldse}; + (* Declare the interpretation *) + if not b then + Symbols.declare_notation_interpretation ntn scope pat df pp8only; + if oldpp8only & not pp8only then + Options.silently + (Symbols.declare_notation_interpretation ntn scope pat df) pp8only; + if not b & not onlyparse then + Symbols.declare_uninterpretation (NotationRule (scope,ntn)) pat + end + +let cache_notation o = + load_notation 1 o; + open_notation 1 o + +let subst_notation (_,subst,(lc,oldse,ntn,scope,(metas,pat),b,b',df)) = + (lc,option_app + (list_smartmap (Extend.subst_syntax_entry Ast.subst_astpat subst)) oldse, + ntn,scope, + (metas,subst_aconstr subst pat), b, b', df) + +let classify_notation (_,(local,_,_,_,_,_,_,_ as o)) = + if local then Dispose else Substitute o + +let export_notation (local,_,_,_,_,_,_,_ as o) = + if local then None else Some o + +let (inNotation, outNotation) = + declare_object {(default_object "NOTATION") with + open_function = open_notation; + cache_function = cache_notation; + subst_function = subst_notation; + load_function = load_notation; + classify_function = classify_notation; + export_function = export_notation} + +(* For old ast printer *) +let rec reify_meta_ast vars = function + | Smetalam (loc,s,body) -> Smetalam (loc,s,reify_meta_ast vars body) +(* | Node(loc,"META",[Num (_,n)]) -> Nmeta (loc,create_meta n)*) + | Node(loc,"ISEVAR",[]) -> Nmeta (loc,"$_") + | Node(loc,op,args) -> Node (loc,op, List.map (reify_meta_ast vars) args) + | Slam(loc,Some id,body) when List.mem id vars -> + Smetalam (loc,string_of_id id,reify_meta_ast vars body) + | Slam(loc,na,body) -> Slam(loc,na,reify_meta_ast vars body) + | Nvar (loc,id) when List.mem id vars -> Nmeta (loc,string_of_id id) + | Nmeta _ | Id _ | Nvar _ | Str _ | Num _ | Path _ as a -> a + | Dynamic _ as a -> (* Hum... what to do here *) a + +(* For old ast syntax *) +let make_old_pp_rule n symbols typs r ntn scope vars = + let ast = Termast.ast_of_rawconstr r in + let ast = reify_meta_ast vars ast in + let scope_name = match scope with Some s -> s | None -> "core_scope" in + let rule_name = ntn^"_"^scope_name^"_notation" in + make_syntax_rule n rule_name symbols typs ast ntn scope + +(* maps positions in v8-notation into positions in v7-notation (used + for parsing). + For instance Notation "x < y < z" := .. V8only "y < z < x" + yields [1; 2; 0] (y is the second arg in v7; z is 3rd; x is fst) *) +let mk_permut vars7 vars8 = + if vars7=vars8 then None else + Some + (List.fold_right + (fun v8 subs -> list_index v8 vars7 - 1 :: subs) + vars8 []) + +let contract_notation ntn = + if ntn = "{ _ }" then ntn else + let rec aux ntn i = + if i <= String.length ntn - 5 then + let ntn' = + if String.sub ntn i 5 = "{ _ }" then + String.sub ntn 0 i ^ "_" ^ + String.sub ntn (i+5) (String.length ntn -i-5) + else ntn in + aux ntn' (i+1) + else ntn in + aux ntn 0 + +let add_notation_in_scope local df c mods omodv8 scope toks = + let ((onlyparse,recs,vars,intnot,notation),prec,(n,typs,symbols,_ as ppdata),df')= + compute_syntax_data !Options.v7 (df,mods) in + (* Declare the parsing and printing rules if not already done *) + (* For both v7 and translate: parsing is as described for v7 in v7 file *) + (* For v8: parsing is as described in v8 file *) + (* For v7: printing is by the old printer - see below *) + (* For translate: printing is as described for v8 in v7 file *) + (* For v8: printing is as described in v8 file *) + (* In short: parsing does not depend on omodv8 *) + (* Printing depends on mv8 if defined, otherwise of mods (scaled by 10) *) + (* if in v7, or of mods without scaling if in v8 *) + let intnot,ntn,pprecvars,ppvars,ppprec,pp_rule = + match omodv8 with + | Some mv8 -> + let (_,recs8,vars8,intnot8,ntn8),p,d,_ = compute_syntax_data false mv8 in + intnot8,ntn8,recs8,vars8,p,make_pp_rule d + | None when not !Options.v7 -> + intnot,notation,recs,vars,prec,make_pp_rule ppdata + | None -> + (* means the rule already exists: recover it *) + (* occurs only with V8only flag alone *) + try + let ntn = contract_notation notation in + let _, oldprec8 = Symbols.level_of_notation ntn in + let rule,_ = Symbols.find_notation_printing_rule ntn in + notation,ntn,recs,vars,oldprec8,rule + with Not_found -> error "No known parsing rule for this notation in V8" + in + let permut = mk_permut vars ppvars in + let gram_rule = make_grammar_rule n typs symbols ntn permut in + Lib.add_anonymous_leaf + (inSyntaxExtension + (local,(Some prec,ppprec),ntn,Some gram_rule,pp_rule)); + + (* Declare interpretation *) + let (acvars,ac) = interp_aconstr [] ppvars c in + let a = (remove_vars pprecvars acvars,ac) (* For recursive parts *) in + let old_pp_rule = + (* Used only by v7; disable if contains a recursive pattern *) + if onlyparse or pprecvars <> [] then None + else + let r = interp_global_rawconstr_with_vars vars c in + Some (make_old_pp_rule n symbols typs r intnot scope vars) in + let onlyparse = onlyparse or !Options.v7_only in + Lib.add_anonymous_leaf + (inNotation(local,old_pp_rule,intnot,scope,a,onlyparse,false,df')) + +let level_rule (n,p) = if p = E then n else max (n-1) 0 + +let check_notation_existence notation = + try + let a,_ = Symbols.level_of_notation (contract_notation notation) in + if a = None then raise Not_found + with Not_found -> + error "Parsing rule for this notation has to be previously declared" + +let exists_notation_syntax ntn = + try fst (Symbols.level_of_notation (contract_notation ntn)) <> None + with Not_found -> false + +let set_data_for_v7_pp recs a vars = + if not !Options.v7 then None else + if recs=[] then Some (a,vars) + else (warning "No recursive notation in v7 syntax";None) + +let build_old_pp_rule notation scope symbs (r,vars) = + let prec = + try + let a,_ = Symbols.level_of_notation (contract_notation notation) in + if a = None then raise Not_found else out_some a + with Not_found -> + error "Parsing rule for this notation has to be previously declared" in + let typs = List.map2 + (fun id n -> + id,ETConstr (NumLevel (level_rule n),InternalProd)) vars (snd prec) in + make_old_pp_rule (fst prec) symbs typs r notation scope vars + +let add_notation_interpretation_core local symbs for_old df a scope onlyparse + onlypp = + let notation = make_notation_key symbs in + let old_pp_rule = + if !Options.v7 then + option_app (build_old_pp_rule notation scope symbs) for_old + else None in + Lib.add_anonymous_leaf + (inNotation(local,old_pp_rule,notation,scope,a,onlyparse,onlypp, + (Lib.library_dp(),df))) + +let add_notation_interpretation df names c sc = + let (recs,vars,symbs) = analyse_notation_tokens (split_notation_string df) in + check_notation_existence (make_notation_key symbs); + let (acvars,ac) = interp_aconstr names vars c in + let a = (remove_vars recs acvars,ac) (* For recursive parts *) in + let a_for_old = interp_rawconstr_with_implicits Evd.empty (Global.env()) vars names c in + let for_oldpp = set_data_for_v7_pp recs a_for_old vars in + add_notation_interpretation_core false symbs for_oldpp df a sc false false + +let add_notation_in_scope_v8only local df c mv8 scope toks = + let (_,recs,vars,intnot,notation),prec,ppdata,df' = compute_syntax_data false (df,mv8) in + let pp_rule = make_pp_rule ppdata in + Lib.add_anonymous_leaf + (inSyntaxExtension(local,(None,prec),notation,None,pp_rule)); + (* Declare the interpretation *) + let onlyparse = false in + let (acvars,ac) = interp_aconstr [] vars c in + let a = (remove_vars recs acvars,ac) (* For recursive parts *) in + Lib.add_anonymous_leaf + (inNotation(local,None,intnot,scope,a,onlyparse,true,df')) + +let add_notation_v8only local c (df,modifiers) sc = + let toks = split_notation_string df in + match toks with + | [String x] when (modifiers = [] or modifiers = [SetOnlyParsing]) -> + (* This is a ident to be declared as a rule *) + add_notation_in_scope_v8only local df c (SetLevel 0::modifiers) sc toks + | _ -> + let (assoc,lev,typs,onlyparse,fmt) = interp_modifiers modifiers in + match lev with + | None-> + if modifiers <> [] & modifiers <> [SetOnlyParsing] then + error "Parsing rule for this notation includes no level" + else + (* Declare only interpretation *) + let (recs,vars,symbs) = analyse_notation_tokens toks in + let onlyparse = modifiers = [SetOnlyParsing] in + let (acvars,ac) = interp_aconstr [] vars c in + let a = (remove_vars recs acvars,ac) in + add_notation_interpretation_core local symbs None df a sc + onlyparse true + | Some n -> + (* Declare both syntax and interpretation *) + let mods = + if List.for_all (function SetAssoc _ -> false | _ -> true) + modifiers + then SetAssoc Gramext.NonA :: modifiers else modifiers in + add_notation_in_scope_v8only local df c mods sc toks + +let is_quoted_ident x = + let x' = unquote_notation_token x in + x <> x' & try Lexer.check_ident x'; true with _ -> false + +let add_notation local c dfmod mv8 sc = + match dfmod with + | None -> add_notation_v8only local c (out_some mv8) sc + | Some (df,modifiers) -> + let toks = split_notation_string df in + match toks with + | [String x] when (modifiers = [] or modifiers = [SetOnlyParsing]) -> + (* This is a ident to be declared as a rule *) + add_notation_in_scope local df c (SetLevel 0::modifiers) mv8 sc toks + | _ -> + let (assoc,lev,typs,onlyparse,fmt) = interp_modifiers modifiers in + match lev with + | None-> + if modifiers <> [] & modifiers <> [SetOnlyParsing] then + error "Parsing rule for this notation includes no level" + else + (* Declare only interpretation *) + let (recs,vars,symbs) = analyse_notation_tokens toks in + if exists_notation_syntax (make_notation_key symbs) then + let onlyparse = modifiers = [SetOnlyParsing] in + let (acvars,ac) = interp_aconstr [] vars c in + let a = (remove_vars recs acvars,ac) in + let a_for_old = interp_global_rawconstr_with_vars vars c in + let for_old = set_data_for_v7_pp recs a_for_old vars in + add_notation_interpretation_core local symbs for_old df a + sc onlyparse false + else + add_notation_in_scope local df c modifiers mv8 sc toks + | Some n -> + (* Declare both syntax and interpretation *) + let assoc = match assoc with None -> Some Gramext.NonA | a -> a in + add_notation_in_scope local df c modifiers mv8 sc toks + +(* TODO add boxes information in the expression *) + +let inject_var x = CRef (Ident (dummy_loc, id_of_string x)) + +let rec rename x vars n = function + | [] -> + (vars,[]) + | String "_"::l -> + let (vars,l) = rename x vars (n+1) l in + let xn = x^(string_of_int n) in + ((inject_var xn)::vars,xn::l) + | String y::l -> + let (vars,l) = rename x vars n l in (vars,(quote_notation_token y)::l) + | WhiteSpace _::l -> + rename x vars n l + +let translate_distfix assoc df r = + let (vars,l) = rename "x" [] 1 (split_notation_string df) in + let df = String.concat " " l in + let a = mkAppC (mkRefC r, vars) in + let assoc = match assoc with None -> Gramext.LeftA | Some a -> a in + (assoc,df,a) + +let add_distfix local assoc n df r sc = + (* "x" cannot clash since r is globalized (included section vars) *) + let (vars,l) = rename "x" [] 1 (split_notation_string df) in + let df = String.concat " " l in + let a = mkAppC (mkRefC r, vars) in + let assoc = match assoc with None -> Gramext.LeftA | Some a -> a in + add_notation_in_scope local df a [SetAssoc assoc;SetLevel n] None sc + (split_notation_string df) + +let make_infix_data n assoc modl mv8 = + (* Infix defaults to LEFTA in V7 (cf doc) *) + let mv = match n with None when !Options.v7 -> SetLevel 1 :: modl | _ -> modl in + let mv = match assoc with None when !Options.v7 -> SetAssoc Gramext.LeftA :: mv | _ -> mv in + let mv8 = match mv8 with + None -> None + | Some(s8,mv8) -> + if List.for_all (function SetLevel _ -> false | _ -> true) mv8 then + error "Needs a level" + else Some (("x "^quote_notation_token s8^" y"),mv8) in + mv,mv8 + +let add_infix local (inf,modl) pr mv8 sc = + if inf="" (* Code for V8Infix only *) then + let (p8,mv8) = out_some mv8 in + let (a8,n8,onlyparse,fmt) = interp_infix_modifiers mv8 in + let metas = [inject_var "x"; inject_var "y"] in + let a = mkAppC (mkRefC pr,metas) in + let df = "x "^(quote_notation_token p8)^" y" in + let toks = split_notation_string df in + if a8=None & n8=None & fmt=None then + (* Declare only interpretation *) + let (recs,vars,symbs) = analyse_notation_tokens toks in + let (acvars,ac) = interp_aconstr [] vars a in + let a' = (remove_vars recs acvars,ac) in + let a_for_old = interp_global_rawconstr_with_vars vars a in + add_notation_interpretation_core local symbs None df a' sc + onlyparse true + else + if n8 = None then error "Needs a level" else + let mv8 = match a8 with None -> SetAssoc Gramext.NonA :: mv8 |_ -> mv8 in + add_notation_in_scope_v8only local df a mv8 sc toks + else begin + let (assoc,n,onlyparse,fmt) = interp_infix_modifiers modl in + (* check the precedence *) + if !Options.v7 & (n<> None & (out_some n < 1 or out_some n > 10)) then + errorlabstrm "Metasyntax.infix_grammar_entry" + (str"Precedence must be between 1 and 10."); + (* + if (assoc<>None) & (n<6 or n>9) then + errorlabstrm "Vernacentries.infix_grammar_entry" + (str"Associativity Precedence must be 6,7,8 or 9."); + *) + let metas = [inject_var "x"; inject_var "y"] in + let a = mkAppC (mkRefC pr,metas) in + let df = "x "^(quote_notation_token inf)^" y" in + let toks = split_notation_string df in + if not !Options.v7 & n=None & assoc=None then + (* En v8, une notation sans information de parsing signifie *) + (* de ne déclarer que l'interprétation *) + (* Declare only interpretation *) + let (recs,vars,symbs) = analyse_notation_tokens toks in + if exists_notation_syntax (make_notation_key symbs) then + let (acvars,ac) = interp_aconstr [] vars a in + let a' = (remove_vars recs acvars,ac) in + let a_for_old = interp_global_rawconstr_with_vars vars a in + let for_old = set_data_for_v7_pp recs a_for_old vars in + add_notation_interpretation_core local symbs for_old df a' sc + onlyparse false + else + let mv,mv8 = make_infix_data n assoc modl mv8 in + add_notation_in_scope local df a mv mv8 sc toks + else + let mv,mv8 = make_infix_data n assoc modl mv8 in + add_notation_in_scope local df a mv mv8 sc toks + end + +let standardise_locatable_notation ntn = + let unquote = function + | String s -> [unquote_notation_token s] + | _ -> [] in + if String.contains ntn ' ' then + String.concat " " + (List.flatten (List.map unquote (split_notation_string ntn))) + else + unquote_notation_token ntn + +(* Delimiters and classes bound to scopes *) +type scope_command = ScopeDelim of string | ScopeClasses of Classops.cl_typ + +let load_scope_command _ (_,(scope,dlm)) = + Symbols.declare_scope scope + +let open_scope_command i (_,(scope,o)) = + if i=1 then + match o with + | ScopeDelim dlm -> Symbols.declare_delimiters scope dlm + | ScopeClasses cl -> Symbols.declare_class_scope scope cl + +let cache_scope_command o = + load_scope_command 1 o; + open_scope_command 1 o + +let subst_scope_command (_,subst,(scope,o as x)) = match o with + | ScopeClasses cl -> + let cl' = Classops.subst_cl_typ subst cl in if cl'==cl then x else + scope, ScopeClasses cl' + | _ -> x + +let (inScopeCommand,outScopeCommand) = + declare_object {(default_object "DELIMITERS") with + cache_function = cache_scope_command; + open_function = open_scope_command; + load_function = load_scope_command; + subst_function = subst_scope_command; + classify_function = (fun (_,obj) -> Substitute obj); + export_function = (fun x -> Some x) } + +let add_delimiters scope key = + Lib.add_anonymous_leaf (inScopeCommand(scope,ScopeDelim key)) + +let add_class_scope scope cl = + Lib.add_anonymous_leaf (inScopeCommand(scope,ScopeClasses cl)) |