
open Ast

type assoc = L | R | N

let level = function
  | "--" -> 70,L
  | "=" -> 70,N
  | "+" -> 60,L
  | "++" -> 60,R
  | "+++" -> 60,R
  | "-" -> 60,L
  | "*" -> 50,L
  | "/" -> 50,L
  | "**" -> 40,R
  | ":" -> (100,R)
  | "->" -> (90,R)
  | s -> failwith ("unknowm operator '"^s^"'")

let fixity = function
  | "--" -> [L]
  | "=" -> [N]
  | ("+"|"-"|"*"|"/") -> [L;N]
  | "++" -> [R]
  | _ -> [L;N;R]

let ground_oper = function
    ("-"|"+") -> true
  | _ -> false

let is_prefix op = List.mem L (fixity op)
let is_infix op = List.mem N (fixity op)
let is_postfix op = List.mem R (fixity op)

let mk_inf op t1 t2 =
  if not (is_infix op) then failwith (op^" not infix");
  Infix(op,t1,t2)

let mk_post op t =
  if not (is_postfix op) then failwith (op^" not postfix");
  Postfix(op,t)


(* Pb avec ground_oper: pas de diff entre -1 et -(1) *)
let mk_pre op t =
  if not (is_prefix op) then failwith (op^" not prefix");
  if ground_oper op then
    match t with
    | Int i -> Int (op^i)
    | _     -> Prefix(op,t)
  else Prefix(op,t)

(* teste si on peut reduire op suivi d'un op de niveau (n,a)
   si la reponse est false, c'est que l'op (n,a) doit se reduire
   avant *)
let red_left_op (nl,al) (nr,ar) =
  if nl < nr then true
  else
    if nl = nr then
      match al,ar with
        | (L|N), L -> true
        | R, (R|N) -> false
        | R, L ->  failwith "conflit d'assoc: ambigu"
        | (L|N), (R|N) -> failwith "conflit d'assoc: blocage"
    else false


type level = int * assoc
type stack =
  | PrefixOper of string list
  | Term of constr_ast * stack
  | Oper of string list * string * constr_ast * stack

let rec str_ast = function
  | Infix(op,t1,t2) -> str_ast t1 ^ " " ^ op ^ " " ^ str_ast t2
  | Postfix(op,t) -> str_ast t ^ " " ^ op
  | Prefix(op,t) -> op ^ " " ^ str_ast t
  | _ -> "_"

let rec str_stack = function
  | PrefixOper ops -> String.concat " " (List.rev ops)
  | Term (t,s) -> str_stack s ^ " (" ^ str_ast t ^ ")"
  | Oper(ops,lop,t,s) ->
      str_stack (Term(t,s)) ^ " " ^ lop ^ " " ^
      String.concat " " (List.rev ops) 

let pps s = prerr_endline (str_stack s)
let err s stk = failwith (s^": "^str_stack stk)


let empty = PrefixOper []

let check_fixity_term stk =
  match stk with
      Term _ -> err "2 termes successifs" stk
    | _ -> ()

let shift_term t stk =
  check_fixity_term stk;
  Term(t,stk)

let shift_oper op stk =
  match stk with
    | Oper(ops,lop,t,s) -> Oper(op::ops,lop,t,s)
    | Term(t,s) -> Oper([],op,t,s)
    | PrefixOper ops -> PrefixOper (op::ops)

let is_reducible lv stk =
  match stk with
    | Oper([],iop,_,_)  -> red_left_op (level iop) lv
    | Oper(op::_,_,_,_) -> red_left_op (level op) lv
    | PrefixOper(op::_) -> red_left_op (level op) lv
    | _                 -> false

let reduce_head (t,stk) =
  match stk with
    | Oper([],iop,t1,s) ->
        (Infix(iop,t1,t), s)
    | Oper(op::ops,lop,t',s) ->
        (mk_pre op t, Oper(ops,lop,t',s))
    | PrefixOper(op::ops) ->
        (Prefix(op,t), PrefixOper ops)
    | _ -> assert false

let rec reduce_level lv (t,s) =
  if is_reducible lv s then reduce_level lv (reduce_head (t, s))
  else (t, s)

let reduce_post op (t,s) =
  let (t',s') = reduce_level (level op) (t,s) in
  (mk_post op t', s')

let reduce_posts stk =
  match stk with
      Oper(ops,iop,t,s) ->
        let pts1 = reduce_post iop (t,s) in
        List.fold_right reduce_post ops pts1
    | Term(t,s) -> (t,s)
    | PrefixOper _ -> failwith "reduce_posts"


let shift_infix op stk =
  let (t,s) = reduce_level (level op) (reduce_posts stk) in
  Oper([],op,t,s)

let is_better_infix op stk =
  match stk with
    | Oper(ops,iop,t,s) ->
        is_postfix iop &&
        List.for_all is_postfix ops &&
        (not (is_prefix op) || red_left_op (level iop) (level op))
    | Term _ -> false
    | _ -> assert false

let parse_oper op stk =
  match stk with
    | PrefixOper _ ->
        if is_prefix op then shift_oper op stk else failwith "prefix_oper"
    | Oper _ ->
        if is_infix op then
          if is_better_infix op stk then shift_infix op stk
          else shift_oper op stk
        else if is_prefix op then shift_oper op stk
        else if is_postfix op then
          let (t,s) = reduce_post op (reduce_posts stk) in
          Term(t,s)
        else assert false
    | Term(t,s) ->
        if is_infix op then shift_infix op stk
        else if is_postfix op then
          let (t2,s2) = reduce_post op (t,s) in Term(t2,s2)
        else failwith "infix/postfix"

let parse_term = shift_term

let rec close_stack stk =
  match stk with
      Term(t,PrefixOper []) -> t
    | PrefixOper _ -> failwith "expression sans atomes"
    | _ ->
        let (t,s) = reduce_head (reduce_posts stk) in
        close_stack (Term(t,s))

