#open "syntaxe";;

exception Échec_filtrage;;

let rec filtrage valeur motif =
  match (valeur, motif) with
    (val, Motif_variable id) -> [id, val]
  | (Val_booléenne b1, Motif_booléen b2) ->
      if b1 = b2 then [] else raise Échec_filtrage
  | (Val_nombre i1, Motif_nombre i2) ->
      if i1 = i2 then [] else raise Échec_filtrage
  | (Val_paire(v1, v2), Motif_paire(m1, m2)) ->
      filtrage v1 m1 @ filtrage v2 m2
  | (Val_nil, Motif_nil) -> []
  | (Val_cons(v1, v2), Motif_cons(m1, m2)) ->
      filtrage v1 m1 @ filtrage v2 m2
  | (_, _) -> raise Échec_filtrage;;
let rec évalue env expr =
  match expr with
    Variable id ->
      begin try
        assoc id env
      with Not_found -> raise(Erreur(id ^ " est inconnu"))
      end
  | Fonction(liste_de_cas) ->
      Val_fermeture {Définition = liste_de_cas; Environnement = env}
  | Application(fonction, argument) ->
      let val_fonction = évalue env fonction in
      let val_argument = évalue env argument in
      begin match val_fonction with
        Val_primitive fonction_primitive ->
          fonction_primitive val_argument
      | Val_fermeture fermeture ->
          évalue_application fermeture.Environnement
                             fermeture.Définition val_argument
      | _ ->
          raise(Erreur "application d'une valeur non fonctionnelle")
      end
  | Let(définition, corps) ->
      évalue (évalue_définition env définition) corps
  | Booléen b -> Val_booléenne b
  | Nombre n -> Val_nombre n
  | Paire(e1, e2) -> Val_paire(évalue env e1, évalue env e2)
  | Nil -> Val_nil
  | Cons(e1, e2) -> Val_cons(évalue env e1, évalue env e2)

and évalue_application env liste_de_cas argument =
  match liste_de_cas with
    [] -> raise(Erreur "échec du filtrage")
  | (motif, expr) :: autres_cas ->
      try
        let env_étendu = filtrage argument motif @ env in
        évalue env_étendu expr
      with Échec_filtrage ->
        évalue_application env autres_cas argument

and évalue_définition env_courant déf =
  match déf.Récursive with
    false -> (déf.Nom, évalue env_courant déf.Expr) :: env_courant
  | true ->
      match déf.Expr with
        Fonction liste_de_cas ->
          let fermeture =
            { Définition = liste_de_cas; Environnement = [] } in
          let env_étendu =
            (déf.Nom, Val_fermeture fermeture) :: env_courant in
          fermeture.Environnement <- env_étendu;
          env_étendu
      | _ -> raise(Erreur "let rec non fonctionnel");;
let rec imprime_valeur = function
    Val_nombre n -> print_int n
  | Val_booléenne false -> print_string "false"
  | Val_booléenne true -> print_string "true"
  | Val_paire(v1, v2) ->
      print_string "("; imprime_valeur v1;
      print_string ", "; imprime_valeur v2;
      print_string ")"
  | Val_nil ->
      print_string "[]"
  | Val_cons(v1, v2) ->
      imprime_valeur v1;
      print_string "::"; imprime_valeur v2
  | Val_fermeture _ | Val_primitive _ ->
      print_string "<fun>";;
