module L = Llvm
module A = Ast

module StringMap = Map.Make(String)

let translate (globals, functions) =
  let context = L.global_context () in
  let the_module = L.create_module context "PieNum"

  and i32_t  = L.i32_type  context
  and i1_t   = L.i1_type   context
  and float_t = L.double_type context
  and i8_t   = L.i8_type   context
  and str_t = L.pointer_type (L.i8_type context)
  and ptr_t  = L.pointer_type (L.i8_type context)
  and array_t = L.array_type
  and void_t = L.void_type context in


  let ltype_of_typ = function
      A.Int -> i32_t
	  | A.Float -> float_t
	  | A.Bool -> i1_t
	  | A.Void -> void_t
      | A.Img -> L.pointer_type i32_t
	  | A.String -> str_t
      | A.ArrayTyp(typ, size) -> (match typ with
          A.Int -> array_t i32_t size
          | A.String -> array_t str_t size
	      | A.Float -> array_t float_t size)
      | A.MatrixTyp(typ, size1, size2) -> (match typ with
      	  A.Int    -> array_t (array_t i32_t size2) size1
      	  | A.Float    -> array_t (array_t float_t size2) size1
		  | A.String -> array_t (array_t str_t size2) size1) in

let global_vars =
    let global_var m (t, n) =
      let init = L.const_int (ltype_of_typ t) 0
      in StringMap.add n (L.define_global n init the_module) m in
    List.fold_left global_var StringMap.empty globals in

(* Built-in functions *)

let read_image = L.function_type (L.pointer_type i32_t) [| L.pointer_type i8_t |] in
let read_image_func = L.declare_function "read_image" read_image the_module in

let write_image = L.function_type i32_t [| L.pointer_type i32_t; i32_t; i32_t; L.pointer_type i8_t |] in
let write_image_func = L.declare_function "write_image" write_image the_module in

let printf_t = L.var_arg_function_type i32_t [| L.pointer_type i8_t |] in
let printf_func = L.declare_function "printf" printf_t the_module in

let printm_float_t      = L.function_type i32_t [| L.pointer_type i8_t; float_t; float_t |] in
let printm_float_func   = L.declare_function "printm_float" printm_float_t the_module in

let conv_to_float = L.function_type float_t [| L.pointer_type i32_t |] in
let conv_to_float_func = L.declare_function "conv_to_float" conv_to_float the_module in

let sleep = L.function_type i32_t [| i32_t |] in
let sleep_func = L.declare_function "sleep" sleep the_module in

let conv_to_float2 = L.function_type float_t [| i32_t |] in
let conv_to_float2_func = L.declare_function "conv_to_float2" conv_to_float2 the_module in

let conv_to_int = L.function_type i32_t [| L.pointer_type float_t |] in
let conv_to_int_func = L.declare_function "conv_to_int" conv_to_int the_module in

let conv_to_int2 = L.function_type i32_t [| float_t |] in
let conv_to_int2_func = L.declare_function "conv_to_int2" conv_to_int2 the_module in

let matrw_float_t       = L.function_type i32_t [| L.pointer_type i8_t; L.pointer_type i8_t; i32_t; i32_t |] in
let matread_float_func  = L.declare_function "matread_float" matrw_float_t the_module in
let matwrite_float_func = L.declare_function "matwrite_float" matrw_float_t the_module in

let function_decls =
    let function_decl m fdecl =
        let name = fdecl.A.fname
        and formal_types = Array.of_list (List.map (fun (t,_) -> ltype_of_typ t) fdecl.A.formals) in
        let ftype = L.function_type (ltype_of_typ fdecl.A.typ) formal_types in
        StringMap.add name (L.define_function name ftype the_module, fdecl) m in
        List.fold_left function_decl StringMap.empty functions in

    let calloc_t = L.function_type ptr_t [|i32_t; i32_t|] in
    let calloc_fun = L.declare_function "calloc" calloc_t the_module in

(* Fill in the body of the given function *)
    let build_function_body fdecl =
    let (the_function, _) = StringMap.find fdecl.A.fname function_decls in
    let builder = L.builder_at_end context (L.entry_block the_function) in

    let int_format_str = L.build_global_stringptr "%d\n" "fmt" builder in
    let str_format_str = L.build_global_stringptr "%s\n" "fmt" builder in
	let float_format_str = L.build_global_stringptr "%f\n" "fmt" builder in

    let int_format_str_inline = L.build_global_stringptr "%d" "fmt" builder in
    let str_format_str_inline = L.build_global_stringptr "%s" "fmt" builder in
    let float_format_str_inline = L.build_global_stringptr "%f" "fmt" builder in

    let string_repeat s n = String.concat "" (Array.to_list (Array.make n s)) in

    (* let array_format_int = L.build_global_stringptr ignore(string_repeat "%d, " 3;) "fmt" builder in *)
    (* Construct the function's "locals": formal arguments and locally
       declared variables.  Allocate each on the stack, initialize their
       value, if appropriate, and remember their values in the "locals" map *)
    let local_vars =
    let add_formal m (t, n) p = L.set_value_name n p;
    let local = L.build_alloca (ltype_of_typ t) n builder in
    ignore (L.build_store p local builder);
    StringMap.add n local m in

    let add_local m (t, n) =
    let local_var = L.build_alloca (ltype_of_typ t) n builder
    in StringMap.add n local_var m in

    let formals = List.fold_left2 add_formal StringMap.empty fdecl.A.formals
      (Array.to_list (L.params the_function)) in
    List.fold_left add_local formals fdecl.A.locals in


	let check_function = List.fold_left (fun m (t, n) -> StringMap.add n t m)
       	StringMap.empty (globals @ fdecl.A.formals @ fdecl.A.locals) in

    let check_function = ref check_function in

    let type_of_val = function
        "i32*" -> int_format_str (*int*)
      | "i8**" -> str_format_str (*string*)
    in


    (* Return the value for a variable or formal argument *)
    let lookup n = try StringMap.find n local_vars
                   with Not_found -> StringMap.find n global_vars
    in

 	let type_of_identifier s =
		let symbols = !check_function in
		StringMap.find s symbols
    in

	let multiply_arr i arr = List.map (fun x -> i *x) arr
	in

	let build_dereference s builder isAssign =
        if isAssign
        then
            L.build_load (lookup s) s builder
        else
            L.build_load (L.build_load (lookup s) s builder) s builder
    in

	let build_reference s builder isArray =
        if isArray
        then
            L.build_in_bounds_gep (lookup s) [| L.const_int i32_t 0; L.const_int i32_t 0 |] s builder
        else
            L.build_in_bounds_gep (lookup s) [| L.const_int i32_t 0; L.const_int i32_t 0 ; L.const_int i32_t 0|] s builder
    in



    let build_pointer_increment s builder isAssign =
        if isAssign
        then
            L.build_load (L.build_in_bounds_gep (lookup s) [| L.const_int i32_t 1 |] s builder) s builder
        else
            L.build_in_bounds_gep (L.build_load (L.build_in_bounds_gep (lookup s) [| L.const_int i32_t 0 |] s builder) s builder) [| L.const_int i32_t 1 |] s builder
    in

    let get_row_type row =
        match (List.hd row) with
            A.FloatLiteral _ -> ltype_of_typ (A.Float)
            | A.IntLiteral _ -> ltype_of_typ (A.Int) in

    let build_row_access s i1 i2 builder isAssign =
        if isAssign
        then L.build_gep (lookup s) [| i1; i2 |] s builder
        else
        L.build_load (L.build_gep (lookup s) [| i1; i2 |] s builder) s builder
    in

  let rec expr1 = function
    A.IntLiteral _ -> A.Int
    | A.ID s -> type_of_identifier s
    | A.Call("to_float", [e])  -> A.Float
    | A.Call("to_int", [e]) -> A.Int
    | A.FloatLiteral _ -> A.Float
    | A.Binop(e1, op, e2) -> let t1 = expr1 e1 and t2 = expr1 e2 in (match op with
        A.Add -> (match t1,t2 with A.Int,A.Int -> A.Int
            | A.Float, A.Float
            | A.Int, A.Float
            | A.Float, A.Int -> A.Float
            | _,_ -> raise (Failure("illegal type")))
        | A.Sub -> (match t1,t2 with A.Int, A.Int -> A.Int
            | A.Float, A.Float | A.Int,A.Float | A.Float, A.Int -> A.Float
            | _,_ -> raise (Failure("illegal type")))
        | A.Mult -> (match t1,t2 with A.Int,A.Int -> A.Int
            | A.Float, A.Float
            | A.Int, A.Float
            | A.Float, A.Int -> A.Float
	        | A.ArrayTyp(typ, size), A.Int
            | A.Int, A.ArrayTyp(typ, size)
            | A.Float, A.ArrayTyp(typ, size)
            | A.ArrayTyp(typ,size), A.Float -> A.ArrayTyp(typ, size)
            | _,_ -> raise (Failure("illegal type")))
        | A.Div -> (match t1,t2 with A.Int,A.Int -> A.Int
            | A.Float,A.Float | A.Int,A.Float | A.Float,A.Int -> A.Float
            | _,_ -> raise (Failure("illegal type")))
        | _ -> raise (Failure ("illegal binop"))) in

    let build_matrix_access s i1 i2 i3 builder isAssign =
        if isAssign
        then L.build_gep (lookup s) [| i1; i2; i3|] s builder
        else L.build_load (L.build_gep (lookup s) [| i1; i2; i3 |] s builder) s builder
    in

    (* Construct code for an expression; return its value *)
    let rec expr builder = function
      A.StringLiteral s -> L.build_global_stringptr s "str_lit" builder
      | A.IntLiteral i -> L.const_int i32_t i
	  | A.FloatLiteral f -> L.const_float float_t f
      | A.ID s -> L.build_load (lookup s) s builder
      | A.BoolLit b -> L.const_int i1_t (if b then 1 else 0)
      | A.ArrayLit r ->  L.const_array (get_row_type r) (Array.of_list (List.map (expr builder) r))
      | A.MatrixLit m -> (match (List.hd (List.hd m)) with
                              | A.IntLiteral _ -> let realOrder=List.map List.rev m in let i32Lists = List.map (List.map (expr builder)) realOrder in let listOfArrays=List.map Array.of_list i32Lists in let i32ListOfArrays = List.map (L.const_array i32_t) listOfArrays in let arrayOfArrays=Array.of_list i32ListOfArrays in L.const_array (array_t i32_t (List.length (List.hd m))) arrayOfArrays

                              | A.FloatLiteral _ -> let realOrder=List.map List.rev m in let ifloatLists = List.map (List.map (expr builder)) realOrder in let listOfArrays=List.map Array.of_list ifloatLists in let ifloatListOfArrays = List.map (L.const_array float_t) listOfArrays in let arrayOfArrays=Array.of_list ifloatListOfArrays in L.const_array (array_t float_t (List.length (List.hd m))) arrayOfArrays)
	  | A.Binop (e1, op, e2) ->
			let e1' = expr builder e1 and
        	e2' = expr builder e2 and
        	t1 = expr1 e1 and
        	t2 = expr1 e2 in

          	let float_bop operator =
            	(match operator with
             		 A.Add     -> L.build_fadd
            		| A.Sub     -> L.build_fsub
            		| A.Mult    -> L.build_fmul
            		| A.Div     -> L.build_fdiv
            		| A.And     -> L.build_and
            		| A.Or      -> L.build_or
            		| A.Equal   -> L.build_fcmp L.Fcmp.Oeq
            		| A.Neq     -> L.build_fcmp L.Fcmp.One
            		| A.Less    -> L.build_fcmp L.Fcmp.Olt
            		| A.Leq     -> L.build_fcmp L.Fcmp.Ole
            		| A.Greater -> L.build_fcmp L.Fcmp.Ogt
            		| A.Geq     -> L.build_fcmp L.Fcmp.Oge
            		| _ -> raise (Failure("Unsupported operator"))
            	 )  e1' e2' "tmp" builder
          	in

          let arr_int_scalar_bop n_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
              (match operator with
                A.Add ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                      let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                      let add_res = L.build_add v1 e2' "tmp" builder in
                      let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                      ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Sub ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                      let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                      let add_res = L.build_sub v1 e2' "tmp" builder in
                      let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                      ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Mult ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_mul v1 e2' "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Div ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_sdiv v1 e2' "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder)
			in


          let arr_int_bop n_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
            let rhs_str = (match e2 with A.ID(s) -> s | _ -> "") in
              (match operator with
                A.Add ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_add v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Sub ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_sub v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Mult ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_mul v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Div ->
                    let tmp_t = L.build_alloca (array_t i32_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_sdiv v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | _ -> raise (Failure("Unsupported operator")))
            in

        let arr_float_bop n_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
            let rhs_str = (match e2 with A.ID(s) -> s | _ -> "") in (match operator with
                A.Add ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_fadd v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Sub ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_fsub v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                  L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Mult ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_fmul v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Div ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                    for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let v2 = build_row_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_fdiv v1 v2 "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | _ -> raise (Failure("Unsupported operator")))
            in

        let matrix_int_scalar_bop r_i c_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
            (match operator with
                A.Add ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_add m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done;
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Sub ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_sub m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done;
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Mult ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_mul m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done;
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Div ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_sdiv m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder)
            in

        let arr_float_scalar_bop n_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in (match operator with
                A.Add ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                        for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_fadd v1 e2' "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                        done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Sub ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                        for i=0 to n_i do
                        let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                        let add_res = L.build_fsub v1 e2' "tmp" builder in
                        let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                        done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Mult ->
                      let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                          for i=0 to n_i do
                          let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                          let add_res = L.build_fmul v1 e2' "tmp" builder in
                          let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                          ignore(L.build_store add_res ld builder);
                          done;
                      L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder
                | A.Div ->
                    let tmp_t = L.build_alloca (array_t float_t n_i) "tmptup" builder in
                        for i=0 to n_i do
                          let v1 = build_row_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) builder false in
                          let add_res = L.build_fdiv v1 e2' "tmp" builder in
                          let ld = L.build_gep tmp_t [| L.const_int i32_t 0; L.const_int i32_t i |] "tmptup" builder in
                        ignore(L.build_store add_res ld builder);
                    done;
                    L.build_load (L.build_gep tmp_t [| L.const_int i32_t 0 |] "tmptup" builder) "tmptup" builder)

			in

        let matrix_float_scalar_bop r_i c_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
            (match operator with
                A.Add ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fadd m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Sub ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fsub m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Mult ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fmul m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Div ->
                let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fdiv m1 e2' "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder)
            in

        let matrix_int_bop r_i c_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
            let rhs_str = (match e2 with A.ID(s) -> s | _ -> "") in
            (match operator with
                A.Add ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_add m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Sub ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_sub m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Mult ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_mul m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Div ->
                    let tmp_m = L.build_alloca (array_t (array_t i32_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_sdiv m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | _ -> raise (Failure("Unsupported operator")))
            in

        let matrix_float_bop r_i c_i operator =
            let lhs_str = (match e1 with A.ID(s) -> s | _ -> "") in
            let rhs_str = (match e2 with A.ID(s) -> s | _ -> "") in
            (match operator with
                A.Add ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fadd m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Sub ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fsub m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Mult ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fmul m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | A.Div ->
                    let tmp_m = L.build_alloca (array_t (array_t float_t c_i) r_i) "tmpmat" builder in
                    for i=0 to (r_i-1) do
                        for j=0 to (c_i-1) do
                            let m1 = build_matrix_access lhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let m2 = build_matrix_access rhs_str (L.const_int i32_t 0) (L.const_int i32_t i) (L.const_int i32_t j) builder false in
                            let add_res = L.build_fdiv m1 m2 "tmp" builder in
                            let ld = L.build_gep tmp_m [| L.const_int i32_t 0; L.const_int i32_t i; L.const_int i32_t j |] "tmpmat" builder in
                            ignore(L.build_store add_res ld builder);
                        done
                    done;
                    L.build_load (L.build_gep tmp_m [| L.const_int i32_t 0 |] "tmpmat" builder) "tmpmat" builder
                | _ -> raise (Failure("Unsupported operator")))
            in


	let int_bop operator =
              (match operator with
              	A.Add     -> L.build_add
            	| A.Sub     -> L.build_sub
            	| A.Mult    -> L.build_mul
            	| A.Div     -> L.build_sdiv
            	| A.And     -> L.build_and
            	| A.Or      -> L.build_or
            	| A.Equal   -> L.build_icmp L.Icmp.Eq
            	| A.Neq     -> L.build_icmp L.Icmp.Ne
            	| A.Less    -> L.build_icmp L.Icmp.Slt
            	| A.Leq     -> L.build_icmp L.Icmp.Sle
            	| A.Greater -> L.build_icmp L.Icmp.Sgt
            	| A.Geq     -> L.build_icmp L.Icmp.Sge
            	| _ -> raise (Failure("Unsupported operator"))
            )  e1' e2' "tmp" builder

			in

				let string_of_e1'_llvalue = L.string_of_llvalue e1'
        		and string_of_e2'_llvalue = L.string_of_llvalue e2' in

        		let space = Str.regexp " " in

        		let list_of_e1'_llvalue = Str.split space string_of_e1'_llvalue
        		and list_of_e2'_llvalue = Str.split space string_of_e2'_llvalue in

        		let i32_re = Str.regexp "i32\\|i32*\\|i8\\|i8*\\|i1\\|i1*"
        		and float_re = Str.regexp "double\\|double*" in

        		let rec match_string regexp str_list i =
         		let length = List.length str_list in
        		 	match (Str.string_match regexp (List.nth str_list i) 0) with
           				true -> true
         				| false -> if (i > length - 2) then false else match_string regexp str_list (succ i) in

        		let get_type llvalue =
           			match (match_string i32_re llvalue 0) with
             			true  -> "int"
           				| false -> (match (match_string float_re llvalue 0) with
                         	true -> "float"
                       		| false -> "") in

				let e1'_type = get_type list_of_e1'_llvalue
        		and e2'_type = get_type list_of_e2'_llvalue in

        		let build_ops_with_types typ1 typ2 =
          			match (typ1, typ2) with
            			"int", "int" -> (match (t1, t2) with A.Int, A.Int -> int_bop op
                                      | A.Bool, A.Bool -> int_bop op
                                      | A.ArrayTyp(A.Int,l1),A.ArrayTyp(A.Int,l2) when l1=l2-> arr_int_bop l1 op
                                      | A.ArrayTyp(A.Int,l1), A.Int -> arr_int_scalar_bop l1 op
                                      | A.MatrixTyp(A.Int, l1, l2), A.Int -> matrix_int_scalar_bop l1 l2 op
                                      | A.MatrixTyp(A.Int,r1,c1),A.MatrixTyp(A.Int,r2,c2) when r1=r2 && c1=c2 -> matrix_int_bop r1 c1 op
                                      | _,_ -> raise (Failure("Cannot build ops with given types")))
          			| "float" , "float" -> (match (t1,t2) with A.Float, A.Float -> float_bop op
                                      | A.MatrixTyp(A.Float,r1,c1),A.MatrixTyp(A.Float,r2,c2) when r1=r2 && c1=c2 -> matrix_float_bop r1 c1 op
                                      | A.ArrayTyp(A.Float,l1),A.ArrayTyp(A.Float,l2) when l1=l2-> arr_float_bop l1 op
                                      | A.ArrayTyp(A.Float,l1), A.Float -> arr_float_scalar_bop l1 op
                                      | A.MatrixTyp(A.Float,r1,c1), A.Float -> matrix_float_scalar_bop r1 c1 op
					  				  | _,_ -> raise (Failure("Cannot build ops with given types")))
          			| _,_ -> raise(Failure("UnsupportedBinop"))
       				in
				build_ops_with_types e1'_type e2'_type
      | A.Unop(op, e) ->
	     let e' = expr builder e in
	      (match op with
	           A.Neg     -> L.build_neg
            | A.Not     -> L.build_not) e' "tmp" builder
      | A.Assign (e1, e2) -> let e1' = ( match e1 with
                                          A.ID s -> L.print_module ; lookup s
										  | A.Dereference(s) -> build_dereference s builder true
 										  | A.MovePointer(s) -> build_pointer_increment s builder true
                                          | A.ArrayAccess(s, e1) -> let i1 = expr builder e1 in build_row_access s (L.const_int i32_t 0) i1 builder true
 					  | A.MatrixAccess(s, e1, e2) -> let i1 = expr builder e1 and i2 = expr builder e2 in build_matrix_access s (L.const_int i32_t 0) i1 i2 builder true)
                              and e2' = expr builder e2 in ignore (L.build_store e2' e1' builder); e2'
      | A.ArrayAccess(s, e1) -> let i1 = expr builder e1 in build_row_access s (L.const_int i32_t 0) i1 builder false
      | A.MatrixAccess(s, e1, e2) -> let i1 = expr builder e1 and i2 = expr builder e2 in build_matrix_access s (L.const_int i32_t 0) i1 i2 builder false
      | A.Dereference(s) -> build_dereference s builder false
      | A.Reference(s) -> build_reference s builder false
      | A.MovePointer(s) -> build_pointer_increment s builder false
	  | A.Call ("sleep", [e]) -> (match e with A.IntLiteral e -> L.build_call sleep_func [| L.const_int i32_t e |] "sleep" builder)
      | A.Call ("prints", [e]) -> L.build_call printf_func [| str_format_str; (expr builder e) |] "printf" builder
      | A.Call ("printsil", [e]) -> L.build_call printf_func [| str_format_str_inline; (expr builder e) |] "printf" builder
      | A.Call ("printi", [e]) -> L.build_call printf_func [| int_format_str; (expr builder e) |] "printf" builder
      | A.Call ("printiil", [e]) -> L.build_call printf_func [| int_format_str_inline; (expr builder e) |] "printf" builder
      | A.Call ("printf", [e]) -> L.build_call printf_func [| float_format_str; (expr builder e) |] "printf" builder
      | A.Call ("printfil", [e]) -> L.build_call printf_func [| float_format_str_inline; (expr builder e) |] "printf" builder
	  | A.Call ("to_float", [e]) ->(match e with
									A.ID e -> L.build_call conv_to_float_func [| lookup e |] "conv_to_float" builder
								   | A.IntLiteral e -> L.build_call conv_to_float2_func [|L.const_int i32_t e|] "conv_to_float2" builder
								   | A.FloatLiteral e -> L.const_float float_t e)
	  | A.Call("to_int", [e]) ->(match e with
									A.ID e -> L.build_call conv_to_int_func [| lookup e |] "conv_to_int" builder
								   | A.FloatLiteral e -> L.build_call conv_to_int2_func [|L.const_float float_t e|] "conv_to_int2" builder
								   | A.IntLiteral e -> L.const_int i32_t e)
	  | A.Call("leni3", [e]) -> (match e with
									A.ID e -> L.const_int i32_t (L.array_length (L.type_of (L.build_load (L.build_gep (lookup e) [| L.const_int i32_t 0 |] e builder) e builder))))
      | A.Call("lenf3", [e]) -> (match e with
                                    A.ID e -> L.const_int i32_t (L.array_length (L.type_of (L.build_load (L.build_gep (lookup e) [| L.const_int i32_t 0 |] e builder) e builder))))
      | A.Call("leni33", [e]) -> (match e with
                                    A.ID e -> L.const_int i32_t (L.array_length (L.type_of (L.build_load (L.build_gep (lookup e) [| L.const_int i32_t 0 |] e builder) e builder))))
      | A.Call("lenf33", [e]) -> (match e with
                                    A.ID e -> L.const_int i32_t (L.array_length (L.type_of (L.build_load (L.build_gep (lookup e) [| L.const_int i32_t 0 |] e builder) e builder))))
	  | A.Call("read_image",[e]) -> L.build_call read_image_func [| expr builder e  |] "read_image" builder
	  | A.Call("write_image",e) -> let actuals= List.rev (List.map (expr builder) (List.rev e)) in
        L.build_call write_image_func (Array.of_list actuals) "write_image" builder
      | A.Call (f, act) ->
          let (fdef, fdecl) = StringMap.find f function_decls in
        	  let actuals = List.rev (List.map (expr builder) (List.rev act)) in

    let result = (match fdecl.A.typ with A.Void -> "" | _ -> f ^ "_result") in
      L.build_call fdef (Array.of_list actuals) result builder  in


      let add_terminal builder f =
      match L.block_terminator (L.insertion_block builder) with
      Some _ -> ()
      | None -> ignore (f builder) in


    let rec stmt builder = function
      A.Block sl -> List.fold_left stmt builder sl
      | A.Expr e -> ignore (expr builder e); builder
      | A.Return e -> ignore (match fdecl.A.typ with
	             A.Void -> L.build_ret_void builder
      | _ -> L.build_ret (expr builder e) builder); builder
      | A.If (predicate, then_stmt, else_stmt) ->
              let bool_val = expr builder predicate in
	             let merge_bb = L.append_block context "merge" the_function in

	              let then_bb = L.append_block context "then" the_function in
	                 add_terminal (stmt (L.builder_at_end context then_bb) then_stmt) (L.build_br merge_bb);

                  	 let else_bb = L.append_block context "else" the_function in
                  	 add_terminal (stmt (L.builder_at_end context else_bb) else_stmt)
                  	   (L.build_br merge_bb);

	 ignore (L.build_cond_br bool_val then_bb else_bb builder);
	 L.builder_at_end context merge_bb

	  | A.For (e1, e2, e3, body) -> stmt builder
      ( A.Block [A.Expr e1 ; A.While (e2, A.Block [body ; A.Expr e3]) ] )

      | A.While (predicate, body) ->
	  let pred_bb = L.append_block context "while" the_function in
	  ignore (L.build_br pred_bb builder);

	  let body_bb = L.append_block context "while_body" the_function in
	  add_terminal (stmt (L.builder_at_end context body_bb) body)
	    (L.build_br pred_bb);

	  let pred_builder = L.builder_at_end context pred_bb in
	  let bool_val = expr pred_builder predicate in

	  let merge_bb = L.append_block context "merge" the_function in
	  ignore (L.build_cond_br bool_val body_bb merge_bb pred_builder);
	  L.builder_at_end context merge_bb
    in

    (* Build the code for each statement in the function *)
    let builder = stmt builder (A.Block fdecl.A.body) in

    (* Add a return if the last block falls off the end*)
    add_terminal builder (match fdecl.A.typ with
        A.Void -> L.build_ret_void
      | t -> L.build_ret (L.const_int (ltype_of_typ t) 0))
  in


List.iter build_function_body functions;
the_module
