Plasma GitLab Archive
Projects Blog Knowledge

(*
 * <COPYRIGHT>
 * Copyright 2002 Joachim Schrod Network and Publication Consultance GmbH, Gerd Stolpmann
 *
 * <GPL>
 * This file is part of WDialog.
 *
 * WDialog is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * WDialog 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WDialog; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * </>
 *)

(* $Id: wd_extension.ml,v 3.4 2002-09-25 00:19:20 stolpmann Exp $
 * ----------------------------------------------------------------------
 *
 *)

open Pcaml;;

(* Syntax extension:
 *
 * You can now write
 *
 * class name ... = interactive object ... end
 *
 * The new keyword "interactive" enables the following special features:
 *
 * - Methods with names "prepare_*" are collected, and a dispatcher method
 *   "prepare_page" is generated and added
 * - Methods with names "handle_*" are collected, and a dispatcher method
 *   "handle" is generated and added
 * Special notations:
 * - _[variable]
 * - _[variable:string]    or _[variable:s]
 * - _[variable:enum]      or _[variable:e]
 * - _[variable:dynenum]   or _[variable:y]
 * - _[variable:alist]     or _[variable:a]
 * - _[variable:dialog]    or _[variable:d]
 * - _[variable.( expr ):string]  (etc.)
 *
 * - _[variable] <- value
 * - _[variable:string] <- value   (etc.)
 *)

(* Note:
 * MLast.CrMth has four arguments until O'Caml 3.04. Later versions take
 * five arguments, as in the following code.
 *)


let find_prepare_page_methods nodes =
  List.map
    (fun node ->
       match node with
	   MLast.CrMth(_,name,_,_,_) ->
	     name
	 | _ ->
	     assert false)
    (List.filter
       (fun node ->
	  match node with
	      MLast.CrMth(_,name,_,_,_) ->
	     	String.length name >= 9 & String.sub name 0 8 = "prepare_"
	    | _ ->
	     	false
       )
       nodes
    )
;;


let find_handle_methods nodes =
  List.map
    (fun node ->
       match node with
	   MLast.CrMth(_,name,_,_,_) ->
	     name
	 | _ ->
	     assert false)
    (List.filter
       (fun node ->
	  match node with
	      MLast.CrMth(_,name,_,_,_) ->
	     	(String.length name >= 8 & String.sub name 0 7 = "handle_")
		or (name = "handle")
	    | _ ->
	     	false
       )
       nodes
    )
;;


let make_prepare_page_dispatcher loc names =
  (* Creates a method as follows:
   * method prepare_page =
   *   match self # page_name with
   *      name1 -> self # prepare_<name1>
   *    | name2 -> self # prepare_<name2>
   *    ...
   *    | _     -> ()
   *)
  let cases =
    List.map
      (fun name ->
	 let pagename = String.sub name 8 (String.length name - 8) in
	 (* <:match_case< $lid:name$ -> self # $lid:methodname$>> *)
	   ( <:patt< $str:pagename$>>,            (* pattern *)
	     None,                                (* "when" clause *)
	     <:expr< self # $name$ ()>>           (* -> expression *)
	   )

      )
      names
    @ [ <:patt< _ >>, None, <:expr< () >> ]
  in
  let names' = List.map (fun s ->
			   let s' =
			     String.sub s 8 (String.length s - 8) in
			   <:expr< $str:s'$ >>
			) names in
  let namelist = Pa_o.mklistexp loc None names' in
  let body = <:expr<
	     let pages = self # page_names in
	     let _ = List.iter (fun n ->
				  if not (List.mem n pages)
				  then
				    raise
				      (Wd_types.Runtime_error
					 ("No page defined for method prepare_" ^ n))
				  else
				    ()
 			       )
	                       $namelist$ in
	     match self # page_name with [ $list:cases$ ]>> in
  <:class_str_item< method prepare_page() = $body$>>
;;


let make_handle_dispatcher loc names =
  (* Creates a method as follows:
   * method handle =
   *   match self # page_name with
   *      name1 -> self # handle_<name1>
   *    | name2 -> self # handle_<name2>
   *    ...
   *    | _     -> ()
   *)
  let cases =
    List.map
      (fun name ->
	 let pagename = String.sub name 7 (String.length name - 7) in
	 (* <:match_case< $lid:name$ -> self # $lid:methodname$>> *)
	   ( <:patt< $str:pagename$>>,            (* pattern *)
	     None,                                (* "when" clause *)
	     <:expr< self # $name$ ()>>           (* -> expression *)
	   )

      )
      names
    @ [ <:patt< _ >>, None, <:expr< () >> ]
  in
  let names' = List.map (fun s ->
			   let s' =
			     String.sub s 7 (String.length s - 7) in
			   <:expr< $str:s'$ >>
			) names in
  let namelist = Pa_o.mklistexp loc None names' in
  let body = <:expr<
 	     let pages = self # page_names in
	     let _ = List.iter (fun n ->
				  if not (List.mem n pages)
				  then
				    raise
				      (Wd_types.Runtime_error
					 ("No page defined for method handle_" ^ n))
				  else
				    ()
 			       )
	                       $namelist$ in
	     match self # page_name with [ $list:cases$ ]>> in
  <:class_str_item< method handle () = $body$>>
;;


let modify_class_structure loc cf =
  let names = find_prepare_page_methods cf in
  (* If there is already a method "prepare_page", output a warning and
   * leave the method as it is.
   *)
  let cf' =
    if List.mem "prepare_page" names then begin
      (* Output warning *)
      !warning loc "This object already defines the method 'prepare_page'; it is not generated";
      cf
    end
    else begin
      (* Add the generated method: *)
      !warning loc ("FYI: Generating method 'prepare_page' that dispatches among: " ^  (String.concat ", " names));
      let m = make_prepare_page_dispatcher loc names in
      m :: cf
    end
  in
  (* Same for "handle" method: *)
  let names = find_handle_methods cf' in
  if List.mem "handle" names then begin
    (* Output warning *)
    !warning loc "This object already defines the method 'handle'; it is not generated";
    cf'
  end
  else begin
    (* Add the generated method: *)
    !warning loc ("FYI: Generating method 'handle' that dispatches among: " ^
		  (String.concat ", " names));
    let m = make_handle_dispatcher loc names in
    m :: cf'
  end

;;


EXTEND
  GLOBAL: class_expr expr;

  (* The new "interactive object": *)

  class_expr: AFTER "simple"
    [ [ "interactive";
	"object";
	cspo = OPT class_self_patt; cf = class_structure; "end" ->
	let cf' = modify_class_structure loc cf in
        <:class_expr< object $cspo$ $list:cf'$ end >>
      ]
    ];

  (* class_self_patt and class_structure are simply copied from the
   * distributed pa_a.ml file of camlp4. We must copy these rules because
   * they are only locally defined.
   *)

  class_self_patt:
    [ [ "("; p = patt; ")" -> p
      | "("; p = patt; ":"; t = ctyp; ")" -> <:patt< ($p$ : $t$) >> ]
    ];
  class_structure:
    [ [ cf = LIST0 class_str_item -> cf ] ];

  (* The :"var"...: expressions: *)

  expr: BEFORE "simple"
    [ [
        "_";"["; name = LIDENT; "]"; "<-" ->
	  <:expr< self # set_variable $str:name$ >>

      | "_";"["; name = LIDENT; ":"; selector = LIDENT; "]"; "<-" ->
	  begin match selector with
	      ("s"|"string") ->
		  <:expr<
		  (fun x ->
		     self # set_variable
		       $str:name$
		       (Wd_types.String_value x))
		  >>
	    | ("e"|"enum") ->
		  <:expr<
		  (fun x ->
		     self # set_variable
		       $str:name$
		       (Wd_types.Enum_value x))
		  >>
	    | ("y"|"dynenum"|"dyn_enum") ->
		  <:expr<
		  (fun x ->
		     self # set_variable
		       $str:name$
		       (Wd_types.Dyn_enum_value x))
		  >>
	    | ("d"|"dialog") ->
		  <:expr<
		  (fun x ->
		     self # set_variable
		       $str:name$
		       (Wd_types.Dialog_value x))
		  >>
	    | ("a"|"alist") ->
		  <:expr<
		  (fun x ->
		     self # set_variable
		       $str:name$
		       (Wd_types.Alist_value x))
		  >>
	    | _ ->
		Stdpp.raise_with_loc loc (Failure ("Unknown selector " ^ selector))
	  end

      | "_";"["; name = LIDENT; "]" ->
	  <:expr< self # variable $str:name$ >>

      | "_";"["; name = LIDENT; ":"; selector = LIDENT; "]" ->
	  begin match selector with
	      ("s"|"string") ->
		  <:expr< self # string_variable $str:name$ >>
	    | ("e"|"enum") ->
		  <:expr< self # enum_variable $str:name$ >>
	    | ("y"|"dynenum"|"dyn_enum") ->
		  <:expr< self # dyn_enum_variable $str:name$ >>
	    | ("d"|"dialog") ->
	  	  <:expr< self # dialog_variable $str:name$ >>
	    | ("a"|"alist") ->
		  <:expr< self # alist_variable $str:name$ >>
	    | _ ->
		Stdpp.raise_with_loc loc (Failure ("Unknown selector " ^ selector))
	  end

      | "_";"["; name = LIDENT; "."; "("; e = expr; ")";
	                    ":"; selector = LIDENT; "]" ->
	  begin match selector with
	      ("s"|"string") ->
		  <:expr< self # lookup_string_variable $str:name$ $e$ >>
	    | ("e"|"enum") ->
		  <:expr< self # lookup_enum_variable $str:name$ $e$ >>
	    | ("y"|"dynenum"|"dyn_enum") ->
		  <:expr< self # lookup_dyn_enum_variable $str:name$ $e$ >>
	    | ("d"|"dialog") ->
	  	  <:expr< self # lookup_dialog_variable $str:name$ $e$ >>
	    | ("a"|"alist") ->
		Stdpp.raise_with_loc loc (Failure "Selector 'alist' not possible after lookup")
	    | _ ->
		Stdpp.raise_with_loc loc (Failure ("Unknown selector " ^ selector))
	  end
      ]

    ];
  END;;

(* ======================================================================
 * History:
 *
 * $Log: wd_extension.ml,v $
 * Revision 3.4  2002-09-25 00:19:20  stolpmann
 * 	Works now (ONLY) for O'Caml 3.06
 *
 * Revision 3.3  2002/02/14 16:16:01  stolpmann
 * 	Added copyright notice.
 *
 * Revision 3.2  2002/02/13 00:17:49  stolpmann
 * 	Updated
 *
 * Revision 3.1  2002/02/12 20:35:03  stolpmann
 * 	Initial revision.
 *
 * Revision 1.1  2002/01/14 15:04:00  gerd
 * 	Syntax change: using _(var) instead of `var`
 *
 * Revision 1.1  2000/04/13 17:43:08  gerd
 * 	Initial revision.
 *
 *
 *)

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml