(***** Langage Mini-C ****)

(** Analyse lexicale **)

(* les tokens *)

type tok_t = PtV | Plus | Prt | DPt (* ; + print : *)
     | Eq | ParG | ParD  (* = ( ) *)
     | Ident of string  (* identifiant *)
     | Num of int  (* nombre *)


let tok_of_string s = 
  (* transforme une chaîne de caractère matchée par la Regexp en token *)
  if s = ";" then PtV
  else if s = "+" then Plus
  else if s = "print" then Prt
  else if s = ":" then DPt
  else if s = "=" then Eq
  else if s = "(" then ParG
  else if s = ")" then ParD
  else if 'a' <= s.[0] && s.[0] <= 'z' then Ident s
  else Num (int_of_string s)
;;

let analyse_lexicale (texte:string) : tok_t list =
  let re = Str.regexp "[a-z]+\\|0\\|[1-9][0-9]*\\|[ \\(\\):;=+\n]" in 
  let rec tokenize texte i =
    if i = String.length texte then
      []
    else 
      let j = Str.search_forward re texte i in
      let str = Str.matched_string texte in
      (* Printf.printf "matched-%s-\n" str; *)
      if str = " " || str = "\n" then
        tokenize texte (j + String.length str)
      else
        (tok_of_string str) :: tokenize texte (j + String.length str)
  in
  tokenize texte 0
;;

assert (analyse_lexicale "x := 3; print(x)" = [Ident "x"; DPt; Eq; Num 3; PtV; Prt; ParG; Ident "x"; ParD]);;
assert (analyse_lexicale "a b+33 c ==" = [Ident "a"; Ident "b"; Plus; Num 33; Ident "c"; Eq; Eq]);;
assert (analyse_lexicale "(aaa(:print:" = [ParG; Ident "aaa"; ParG; DPt; Prt; DPt]);;

(** Analyse syntaxique **)

(* arbre de syntaxe abstraite *)

type progr_t = Uniq of instr_t | Seq of instr_t * progr_t

and instr_t = Affiche of expr_t | Affect of string * expr_t

and expr_t = Val of string | Add of expr_t * expr_t | Entier of int


let rec parse_progr l = 
  let instr, ll = parse_instr l in (* on parse une instruction *)
  match ll with (* en fonction du flux restant ...*)
  | [PtV] -> Uniq instr, []  (* c'était la dernière instruction *)
  | PtV :: lll -> let prog, llll = parse_progr lll in Seq (instr, prog), llll
  | _ -> failwith "Erreur1"
and parse_instr l = 
match l with 
  | Prt :: ParG :: ll -> (* c'est un print ( ... *)
    begin
    let expr, lll = parse_expr ll in (* on parse une expr *)
    (* il reste à lire une ) *)
    match lll with
    | ParD :: llll ->  Affiche expr, llll  
    | _ -> failwith "Erreur2" 
    end
  | Ident v :: DPt :: Eq :: ll ->  (* c'est une affectation *)
    let expr, lll = parse_expr ll in (* on parse une expr *)
    Affect (v, expr), lll
  | _ -> failwith "Erreur3"
and parse_expr l =
  (* on parse un identifiant ou un entier *)
let e1, ll = match l with
  | Ident v :: ll  -> Val v, ll
  | Num n :: ll -> Entier n, ll
  | _ -> failwith "Erreur4"
in 
  match ll with
  (* si la suite commence par +, on parse à nouveau une expression*)
  | Plus :: lll -> let e2, llll = parse_expr lll in Add (e1, e2), llll
  (* sinon on a fini de parser l'expression *)
  | _ -> e1, ll
;;


let analyse_syntaxique tok_list = 
  (* on parse le programme à partir de la liste de tokens *)
  let prog, l = parse_progr tok_list in
  (* on vérifie que tous les tokens ont éété consommés *)
  if l = [] then prog else failwith "Erreur6"
;;


(** Evaluation de l'arbre de syntaxe abstraite **)

(* Environnement d'évaluation *)

let new_env () = Hashtbl.create 10;;

let get_val env nom = Hashtbl.find env nom;;

let set_val env nom v = Hashtbl.add env nom v;;

(* Fonctions d'évaluation, récursive sur la structure de l'arbre, 
   en utilisant l'environnement *)

let rec evalue_progr progr env = match progr with
| Uniq instr -> evalue_instr instr env
| Seq (instr, prg) ->  evalue_instr instr env; evalue_progr prg env
and evalue_instr instr env = match instr with
| Affiche e -> let v = evalue_expr e env in Printf.printf "%d\n" v
| Affect(nom, e) -> let v = evalue_expr e env in set_val env nom v
and evalue_expr expr env = match expr with
| Entier n -> n 
| Val nom -> get_val env nom
| Add (e1, e2) -> let v1 = evalue_expr e1 env in let v2 = evalue_expr e2 env in v1 + v2
;;


(** Fonction de lecture, tokenisation, parsing, puis évaluation **)

let interprete filename =

  (* lecture du code source fichier *)
  let ch = open_in filename in
  let texte = ref "" in
  begin
  try 
    while true do
      let ligne = input_line ch in
      texte := !texte ^ ligne ^ "\n"
    done
  with
    End_of_file -> ()
  end;
  close_in ch;
  (* print_endline !texte; *)

  (* analyse lexicale *)
  let tok_list = analyse_lexicale !texte in

  (* analyse syntaxique *)
  let arbre = analyse_syntaxique tok_list in

  (* evaluation *)
  evalue_progr arbre (new_env ())
;;

interprete Sys.argv.(1);;