Version française
Home     About     Download     Resources     Contact us    
Browse thread
Two Different Exception Behaviors in camlp4 on the toplevel
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: blue storm <bluestorm.dylc@g...>
Subject: Re: [Caml-list] Two Different Exception Behaviors in camlp4 on the toplevel
The reason you have two different kinds of error is that you use
type_ast in two different places :

  (* Converts a calculator AST into an OCaml AST *)
  let to_expr base_loc prog=
     let e=CalcGram.parse_string term_eoi base_loc prog in
     let _= type_ast e in
     let rec to_expr e= ... in
     let e= to_expr e in ...
     <:expr< let _ = type_ast $e$ in $e$ >>
  ;;

The first occurence of "type_ast" in that code is executed at
parsing-time. The second occurence, inside <:expr< ... >>, is executed
at runtime, like a classical OCaml exception, and the toplevel does no
special error reporting on Camlp4-related exceptions.

You could fix this by inserting (at runtime) a call to the Camlp4
exception printer :
   <:expr< let _ =
             try type_ast $e$ with exn ->
               Format.eprintf "%a@." Camlp4.ErrorHandler.print exn;
               raise exn in
           $e$ >>

Unfortunately, I'm not sure it will work in the toplevel (wich has a
special location handling as there is no "file" associated to
locations). It will work however if you #use a file containing an
erroneous code.

$ cat foo.ml
open Camlp4.PreCast
open Calc
let _ = <:calc< 1 + (2 + $"<:calc< true >>"$) >>

$ ocamlc -pp 'camlp4o calc.cmo' -I +camlp4 dynlink.cma camlp4lib.cma
calc.cmo foo.ml -o foo
$ ./foo
File "foo.ml", line 3, characters 21-45:
Camlp4: Uncaught exception: Calc.Type_error
Fatal error: exception Loc.Exc_located(_, _)

$ ocaml dynlink.cma camlp4o.cma calc.cmo
# #use "foo.ml";;
File "foo.ml", line 3, characters 21-45:
Camlp4: Uncaught exception: Calc.Type_error
Exception: Loc.Exc_located (<abstr>, Calc.Type_error).




You are actually doing unecessary work. The OCaml type system being
quite powerful, it is easy to have it doing the work, instead of
coding your own limited typechecker. The idea is that you should only
generate OCaml terms that are typed exactly when you want them to be
typed.

(** We use a RuntimeCalc module with a typed interface to enforce correct types
    for calc values *)
module RuntimeCalc : sig
  type calc =
      [ `Nonterm of [ `Add | `Sub | `Or | `And ] * calc list
      | `Term of [ `Int of int | `Bool of bool ] ]
  type 'a t
  val add : int t list -> int t
  val sub : int t list -> int t
  val or_ : bool t list -> bool t
  val and_ : bool t list -> bool t
  val int : int -> int t
  val bool : bool -> bool t
  val expose : 'a t -> calc
end = struct
  type calc =
      [ `Nonterm of [ `Add | `Sub | `Or | `And ] * calc list
      | `Term of [ `Int of int | `Bool of bool ] ]
  type 'a t = calc
  let expose calc = calc
  let op name li = `Nonterm (name, li)
  let add li = op `Add li
  let sub li = op `Sub li
  let and_ li = op `And li
  let or_ li = op `Or li
  let int n = `Term (`Int n)
  let bool b = `Term (`Bool b)
  let expose t = t
end

(* Generates an expression with the nonterminal information *)
let expr_of_nonterm _loc e=
   match e with
   | `Add -> <:expr< RuntimeCalc.add >>
   | `Sub -> <:expr< RuntimeCalc.sub >>
   | `Or -> <:expr< RuntimeCalc.or_ >>
   | `And -> <:expr< RuntimeCalc.and_ >>
;;

(* Generates an expression with the terminal information *)
let expr_of_term _loc e=
   match e with
   | `Int i -> <:expr< RuntimeCalc.int $`int:i$ >>
   | `Bool b -> <:expr< RuntimeCalc.bool $`bool:b$ >>
   | `Ocaml(l,e) -> Gram.parse_string Syntax.expr_eoi l e
;;

(* Converts a calculator AST into an OCaml AST *)
let to_expr _loc prog =
   let rec to_expr : calc -> Ast.expr = function
     | `Nonterm (_loc, name, nodes) ->
         let nodes= expr_of_list _loc (List.map to_expr nodes) in
         let name = expr_of_nonterm _loc name in
         <:expr< $name$ $nodes$ >>
     | `Term(_loc, term) ->
         expr_of_term _loc term in
   let e = to_expr (CalcGram.parse_string term_eoi _loc prog) in
   <:expr< $e$ >>
;;

In that example, I put RuntimeCalc inside the Calc module for
convenience (and similarity with your code). You should really put it
outside, in a separated module, and keep a strong separation between
camlp4-time code and runtime code.