(* Pretty Printer *)
open Ast
open Sast
open Parser
open Processor
open Yojson

let save file string =
	 let channel = open_out file in
	 output_string channel string;
	 close_out channel

let replace input output =
	Str.global_replace (Str.regexp_string input) output
	
(* Print data types *)

let string_of_scope = function 
		Public 	-> "public"
	| 	Private -> "private"

let string_of_primitive = function 
		Int_t 						-> "int"
	| 	Float_t 					-> "float"
	| 	Void_t						-> "void"
	| 	Bool_t 						-> "bool"
	| 	Char_t 						-> "char"
	| 	Objecttype(s)				-> "class " ^ s
	| 	ConstructorType				-> "constructor"
	|  	Null_t 						-> "null"

let string_of_object = function
		Datatype(Objecttype(s))	-> s
	| 	_ -> ""

let rec print_brackets = function
		1 -> "[]"
	| 	a -> "[]" ^ print_brackets (a - 1)
		
let string_of_datatype = function 
		Arraytype(p, i)	-> (string_of_primitive p) ^ (print_brackets i)
	| 	Datatype(p)		-> (string_of_primitive p)
	|  	Any 			-> "Any"

(* Print expressions *)

let string_of_op = function
		Add			-> "+"	
	 | 	Sub			-> "-"	
	 | 	Mult		-> "*"	
	 | 	Div			-> "/"	
	 | 	Equal		-> "=="		
	 | 	Neq			-> "!="	
	 | 	Less		-> "<"	
	 | 	Leq			-> "<="	
	 | 	Greater		-> ">"			
	 | 	Geq			-> ">="	
	 | 	And			-> "and"	
	 | 	Not			-> "not"	
	 | 	Or			-> "or"
	 | 	Mod 		-> "%"

let rec string_of_bracket_expr = function
		[] 				-> ""
	| 	head :: tail 	-> "[" ^ (string_of_expr head) ^ "]" ^ (string_of_bracket_expr tail)
and string_of_array_primitive = function
		[] 				-> ""
	|   [last]			-> (string_of_expr last)
	| 	head :: tail 	-> (string_of_expr head) ^ ", " ^ (string_of_array_primitive tail)
and string_of_expr = function 
		Int_Lit(i)				-> string_of_int i
	|	Boolean_Lit(b)			-> if b then "true" else "false"
	|	Float_Lit(f)			-> string_of_float f
	|	String_Lit(s)			-> "\"" ^ (String.escaped s) ^ "\""
	|	Char_Lit(c)				-> Char.escaped c
	|	This					-> "this"
	|	Id(s)					-> s
	|	Binop(e1, o, e2)		-> (string_of_expr e1) ^ " " ^ (string_of_op o) ^ " " ^ (string_of_expr e2)
	|	Assign(e1, e2)			-> (string_of_expr e1) ^ " = " ^ (string_of_expr e2)
	|	Noexpr					-> ""
	|	ObjAccess(e1, e2)		-> (string_of_expr e1) ^ "." ^ (string_of_expr e2)
	|	Call(f, el)				-> f ^ "(" ^ String.concat ", " (List.map string_of_expr el) ^ ")"
	|	ArrayPrimitive(el)		-> "|" ^ (string_of_array_primitive el) ^ "|"
	|  	Unop(op, e)				-> (string_of_op op) ^ "(" ^ string_of_expr e ^ ")"
	|	Null					-> "null"
	|   ArrayCreate(d, el)  	-> "new " ^ string_of_datatype d ^ string_of_bracket_expr el
	|   ArrayAccess(e, el)  	-> (string_of_expr e) ^ (string_of_bracket_expr el)
	|   ObjectCreate(s, el) 	-> "new " ^ s ^ "(" ^ String.concat ", " (List.map string_of_expr el) ^ ")"
	| 	Delete(e) 				-> "delete (" ^ (string_of_expr e) ^ ")"
;;

let rec string_of_bracket_sexpr = function
		[] 				-> ""
	| 	head :: tail 	-> "[" ^ (string_of_sexpr head) ^ "]" ^ (string_of_bracket_sexpr tail)
and string_of_sarray_primitive = function
		[] 				-> ""
	|   [last]			-> (string_of_sexpr last)
	| 	head :: tail 	-> (string_of_sexpr head) ^ ", " ^ (string_of_sarray_primitive tail)
and string_of_sexpr = function 
		SInt_Lit(i)					-> string_of_int i
	|	SBoolean_Lit(b)				-> if b then "true" else "false"
	|	SFloat_Lit(f)				-> string_of_float f
	|	SString_Lit(s)				-> "\"" ^ (String.escaped s) ^ "\""
	|	SChar_Lit(c)				-> Char.escaped c
	|	SId(s, _)					-> s
	|	SBinop(e1, o, e2, _)		-> (string_of_sexpr e1) ^ " " ^ (string_of_op o) ^ " " ^ (string_of_sexpr e2)
	|	SAssign(e1, e2, _)			-> (string_of_sexpr e1) ^ " = " ^ (string_of_sexpr e2)
	|	SNoexpr						-> ""
	|	SObjAccess(e1, e2, _)		-> (string_of_sexpr e1) ^ "." ^ (string_of_sexpr e2)
	|	SCall(f, el, _, _)			-> f ^ "(" ^ String.concat ", " (List.map string_of_sexpr el) ^ ")"
	|	SArrayPrimitive(el, _)		-> "|" ^ (string_of_sarray_primitive el) ^ "|"
	|  	SUnop(op, e, _)				-> (string_of_op op) ^ "(" ^ string_of_sexpr e ^ ")"
	|	SNull						-> "null"
	|   SArrayCreate(d, el, _)  	-> "new " ^ string_of_datatype d ^ string_of_bracket_sexpr el
	|   SArrayAccess(e, el, _)  	-> (string_of_sexpr e) ^ (string_of_bracket_sexpr el)
	|   SObjectCreate(s, el, _) 	-> "new " ^ s ^ "(" ^ String.concat ", " (List.map string_of_sexpr el) ^ ")"
	| 	SDelete(e) 					-> "delete (" ^ (string_of_sexpr e) ^ ")"
;;

let string_of_local_expr = function
		Noexpr -> ""
	|  	e 	   -> " = " ^ string_of_expr e

(* Print statements *)

let rec string_of_stmt indent =
	let indent_string = String.make indent '\t' in
	let get_stmt_string = function 

			Block(stmts) 			-> 
				indent_string ^ "{\n" ^ 
					String.concat "" (List.map (string_of_stmt (indent+1)) stmts) ^ 
				indent_string ^ "}\n"

		| 	Expr(expr) 				-> 
				indent_string ^ string_of_expr expr ^ ";\n";

		| 	Return(expr) 			-> 
				indent_string ^ "return " ^ string_of_expr expr ^ ";\n";

		| 	If(e, s, Block([Expr(Noexpr)])) 	-> 
				indent_string ^ "if (" ^ string_of_expr e ^ ")\n" ^ 
					(string_of_stmt (indent+1) s)

		| 	If(e, s1, s2) 			-> 
				indent_string ^ "if (" ^ string_of_expr e ^ ")\n" ^ 
					string_of_stmt (indent+1) s1 ^ 
				indent_string ^ "else\n" ^ 
					string_of_stmt (indent+1) s2

		| 	For(e1, e2, e3, s) 		-> 
				indent_string ^ "for (" ^ string_of_expr e1  ^ " ; " ^ string_of_expr e2 ^ " ; " ^ string_of_expr e3  ^ ")\n" ^ 
					string_of_stmt (indent) s

		| 	While(e, s) 			-> 
				indent_string ^ "while (" ^ string_of_expr e ^ ")\n" ^ 
					string_of_stmt (indent) s

		|  	Break					-> indent_string ^ "break;\n"
		|  	Continue				-> indent_string ^ "continue;\n"
		|   Local(d, s, e) 			-> indent_string ^ string_of_datatype d ^ " " ^ s ^ string_of_local_expr e ^ ";\n"
	in get_stmt_string

let string_of_local_sexpr = function
		SNoexpr 	-> ""
	|  	e 	   		-> " = " ^ string_of_sexpr e

let rec string_of_sstmt indent =
	let indent_string = String.make indent '\t' in
	let get_stmt_string = function 

			SBlock(stmts) 			-> 
				indent_string ^ "{\n" ^ 
					String.concat "" (List.map (string_of_sstmt (indent+1)) stmts) ^ 
				indent_string ^ "}\n"

		| 	SExpr(expr, _) 				-> 
				indent_string ^ string_of_sexpr expr ^ ";\n";

		| 	SReturn(expr, _) 			-> 
				indent_string ^ "return " ^ string_of_sexpr expr ^ ";\n";

		| 	SIf(e, s, SBlock([SExpr(SNoexpr, _)])) 	-> 
				indent_string ^ "if (" ^ string_of_sexpr e ^ ")\n" ^ 
					(string_of_sstmt (indent+1) s)

		| 	SIf(e, s1, s2) 			-> 
				indent_string ^ "if (" ^ string_of_sexpr e ^ ")\n" ^ 
					string_of_sstmt (indent+1) s1 ^ 
				indent_string ^ "else\n" ^ 
					string_of_sstmt (indent+1) s2

		| 	SFor(e1, e2, e3, s) 		-> 
				indent_string ^ "for (" ^ string_of_sexpr e1  ^ " ; " ^ string_of_sexpr e2 ^ " ; " ^ string_of_sexpr e3  ^ ")\n" ^ 
					string_of_sstmt (indent) s

		| 	SWhile(e, s) 			-> 
				indent_string ^ "while (" ^ string_of_sexpr e ^ ")\n" ^ 
					string_of_sstmt (indent) s

		|  	SBreak					-> indent_string ^ "break;\n"
		|  	SContinue				-> indent_string ^ "continue;\n"
		|   SLocal(d, s, e) 			-> indent_string ^ string_of_datatype d ^ " " ^ s ^ string_of_local_sexpr e ^ ";\n"
	in get_stmt_string

(* Print Function *)

let string_of_fname = function 
		Constructor -> "constructor"
	|	FName(s)	-> s

let string_of_formal = function
		Formal(d, s) -> (string_of_datatype d) ^ " " ^ s
	|  	_ 			 -> ""

let string_of_formal_name = function
		Formal(_, s) -> s
	| 	_ -> ""

let string_of_func_decl fdecl =
	"" ^ (string_of_scope fdecl.scope) ^ " " ^ (string_of_datatype fdecl.returnType) ^ " " ^ (string_of_fname fdecl.fname) ^ " " ^ 
	(* Formals *)
	"(" ^ String.concat "," (List.map string_of_formal fdecl.formals) ^ ") {\n" ^
		(* body *)
		String.concat "" (List.map (string_of_stmt 2) fdecl.body) ^
	"\t}\n\n"

(* Class Printing *)

let string_of_extends = function 
		NoParent	-> ""
	| 	Parent(s)	-> "extends " ^ s ^ " " 
let string_of_field = function 
	Field(s, d, id) -> (string_of_scope s) ^ " " ^ (string_of_datatype d) ^ " " ^ id ^ ";\n"

let string_of_cbody cbody = 
	String.concat "" (List.map (fun s -> "\t" ^ s) (List.map string_of_field cbody.fields)) ^
	String.concat "" (List.map (fun s -> "\t" ^ s) (List.map string_of_func_decl cbody.constructors)) ^
	String.concat "" (List.map (fun s -> "\t" ^ s) (List.map string_of_func_decl cbody.methods))

let string_of_class_decl cdecl = 
	"class " ^ cdecl.cname ^ " " ^ (string_of_extends cdecl.extends) ^ "{\n" ^
	(string_of_cbody cdecl.cbody) ^
	"}\n"

(* Include Printing *)

let rec string_of_include = function 
	Include(s) -> "include(" ^ s ^ ");\n"

(* Print whole program *)

let string_of_program = function
	Program(includes, cdecls) -> 
		String.concat "" (List.map string_of_include includes) ^ "\n" ^
		String.concat "\n" (List.map string_of_class_decl cdecls)

(* Print AST tree representation *)

let includes_tree includes = 
	`List (List.map (function Include s -> `String s) includes)

let map_fields_to_json fields = 
	`List (List.map (function Field(scope, datatype, s) -> 
		`Assoc [
			("name", `String s);
			("scope", `String (string_of_scope scope));
			("datatype", `String (string_of_datatype datatype));
		]) fields)

let map_formals_to_json formals = 
	`List (List.map (function Formal(d, s) -> `Assoc [
													("name", `String s);
													("datatype", `String (string_of_datatype d));
												]
							  | Many d -> `Assoc [("Many", `String (string_of_datatype d));]
		) formals)

let rec map_expr_to_json = function 
		Int_Lit(i)				-> `Assoc [("int_lit", `Int i)]
	|	Boolean_Lit(b)			-> `Assoc [("bool_lit", `Bool b)]
	|	Float_Lit(f)			-> `Assoc [("float_lit", `Float f)]
	|	String_Lit(s)			-> `Assoc [("string_lit", `String s)]
	|	Char_Lit(c)				-> `Assoc [("char_lit", `String (Char.escaped c))]
	|	This					-> `String "this"
	|	Id(s)					-> `Assoc [("id", `String s)]
	|	Binop(e1, o, e2)		-> `Assoc [("binop", `Assoc [("lhs", map_expr_to_json e1); ("op", `String (string_of_op o)); ("rhs", map_expr_to_json e2)])]
	|	Assign(e1, e2)			-> `Assoc [("assign", `Assoc [("lhs", map_expr_to_json e1); ("op", `String "="); ("rhs", map_expr_to_json e2)])]
	|	Noexpr					-> `String "noexpr"
	|	ObjAccess(e1, e2)		-> `Assoc [("objaccess", `Assoc [("lhs", map_expr_to_json e1); ("op", `String "."); ("rhs", map_expr_to_json e2)])]
	|	Call(f, el)				-> `Assoc [("call", `Assoc ([("name", `String f); ("params", `List (List.map map_expr_to_json el)); ]) )]
	|	ArrayPrimitive(el)		-> `Assoc [("arrayprimitive", `List(List.map map_expr_to_json el))]
	|  	Unop(op, e)				-> `Assoc [("Unop", `Assoc [("op", `String (string_of_op op)); ("operand", map_expr_to_json e)])]
	|	Null					-> `String "null"
	|   ArrayCreate(d, el)  	-> `Assoc [("arraycreate", `Assoc [("datatype", `String (string_of_datatype d)); ("args", `List (List.map map_expr_to_json el))])]
	|   ArrayAccess(e, el)  	-> `Assoc [("arrayaccess", `Assoc [("array", map_expr_to_json e); ("args", `List (List.map map_expr_to_json el))])]
	|   ObjectCreate(s, el) 	-> `Assoc [("objectcreate", `Assoc [("type", `String s); ("args", `List (List.map map_expr_to_json el))])]
	| 	Delete(e) 				-> `Assoc [("delete", `Assoc [("expr", map_expr_to_json e)])]

let rec map_stmt_to_json = function
		Block(stmts) 			-> `Assoc [("block", `List (List.map (map_stmt_to_json) stmts))]
	| 	Expr(expr) 				-> `Assoc [("expr", map_expr_to_json expr)]
	| 	Return(expr) 			-> `Assoc [("return", map_expr_to_json expr)]
	| 	If(e, s1, s2) 			-> `Assoc [("if", `Assoc [("cond", map_expr_to_json e); ("ifbody", map_stmt_to_json s1)]); ("else", map_stmt_to_json s2)]
	| 	For(e1, e2, e3, s) 		-> `Assoc [("for", `Assoc [("init", map_expr_to_json e1); ("cond", map_expr_to_json e2); ("inc", map_expr_to_json e3); ("body", map_stmt_to_json s)])]
	| 	While(e, s) 			-> `Assoc [("while", `Assoc [("cond", map_expr_to_json e); ("body", map_stmt_to_json s)])]
	|  	Break					-> `String "break"
	|  	Continue				-> `String "continue"
	|   Local(d, s, e) 			-> `Assoc [("local", `Assoc [("datatype", `String (string_of_datatype d)); ("name", `String s); ("val", map_expr_to_json e)])]

let map_methods_to_json methods = 
	`List (List.map (fun (fdecl:Ast.func_decl) -> 
		`Assoc [
			("name", `String (string_of_fname fdecl.fname));
			("scope", `String (string_of_scope fdecl.scope));
			("returnType", `String (string_of_datatype fdecl.returnType));
			("formals", map_formals_to_json fdecl.formals);
			("body", `List (List.map (map_stmt_to_json) fdecl.body));
		]) methods)


let cdecls_tree cdecls =
	let map_cdecl_to_json cdecl = 
		`Assoc [
			("cname", `String cdecl.cname);
			("extends", `String (string_of_extends cdecl.extends));
			("fields", map_fields_to_json cdecl.cbody.fields);
			("methods", map_methods_to_json cdecl.cbody.methods);
			("constructors", map_methods_to_json cdecl.cbody.constructors)
		]
	in
	`List (List.map (map_cdecl_to_json) cdecls)

let print_tree = function
	Program(includes, cdecls) -> 
		`Assoc [("program", 
			`Assoc([
				("includes", includes_tree includes);
				("classes", cdecls_tree cdecls)
			])
		)]

(* Print SAST tree representation *)

let rec map_sexpr_to_json = 
	let datatype d = [("datatype", `String (string_of_datatype d))] in
	function
		SInt_Lit(i)          	-> `Assoc [("int_lit", `Assoc ([("val", `Int i)] @ (datatype (Datatype(Int_t)))))]
	|   SBoolean_Lit(b)      	-> `Assoc [("bool_lit", `Assoc ([("val", `Bool b)] @ (datatype (Datatype(Bool_t)))))]
	|   SFloat_Lit(f)        	-> `Assoc [("float_lit", `Assoc ([("val", `Float f)]  @ (datatype (Datatype(Float_t)))))]
	|   SString_Lit(s)       	-> `Assoc [("string_lit", `Assoc ([("val", `String s)] @ (datatype (Arraytype(Char_t, 1)))))]
	|   SChar_Lit(c)         	-> `Assoc [("char_lit", `Assoc ([("val", `String (Char.escaped c))] @ (datatype (Datatype(Char_t)))))]
	|   SId(s, d)               -> `Assoc [("id", `Assoc ([("name", `String s)] @ (datatype d)))]
	|   SBinop(e1, o, e2, d)    -> `Assoc [("binop", `Assoc ([("lhs", map_sexpr_to_json e1); ("op", `String (string_of_op o)); ("rhs", map_sexpr_to_json e2)] @ (datatype d)))]
	|   SAssign(e1, e2, d)      -> `Assoc [("assign", `Assoc ([("lhs", map_sexpr_to_json e1); ("op", `String "="); ("rhs", map_sexpr_to_json e2)] @ (datatype d)))]
	|   SNoexpr              	-> `Assoc [("noexpr", `Assoc (datatype (Datatype(Void_t))))]
	|   SArrayCreate(t, el, d)  -> `Assoc [("arraycreate", `Assoc ([("datatype", `String (string_of_datatype d)); ("args", `List (List.map map_sexpr_to_json el))] @ (datatype d)))]
	|   SArrayAccess(e, el, d)  -> `Assoc [("arrayaccess", `Assoc ([("array", map_sexpr_to_json e); ("args", `List (List.map map_sexpr_to_json el))] @ (datatype d)))]
	|   SObjAccess(e1, e2, d)   -> `Assoc [("objaccess", `Assoc ([("lhs", map_sexpr_to_json e1); ("op", `String "."); ("rhs", map_sexpr_to_json e2)] @ (datatype d)))]
	|   SCall(fname, el, d, i)  -> `Assoc [("call", `Assoc ([("name", `String fname); ("params", `List (List.map map_sexpr_to_json el)); ("index", `Int i) ] @ (datatype d)) )]
	|   SObjectCreate(s, el, d) -> `Assoc [("objectcreate", `Assoc ([("type", `String s); ("args", `List (List.map map_sexpr_to_json el))] @ (datatype d)))]
	|   SArrayPrimitive(el, d)  -> `Assoc [("arrayprimitive", `Assoc ([("expressions", `List(List.map map_sexpr_to_json el))] @ (datatype d)))]
	|   SUnop(op, e, d)         -> `Assoc [("Unop", `Assoc ([("op", `String (string_of_op op)); ("operand", map_sexpr_to_json e)] @ (datatype d)))]
	|   SNull               	-> `Assoc [("null", `Assoc (datatype (Datatype(Void_t))))] 
	| 	SDelete(e) 				-> `Assoc [("delete", `Assoc ([("expr", map_sexpr_to_json e)] @ (datatype (Datatype(Void_t)))))]

let rec map_sstmt_to_json = 
	let datatype d = [("datatype", `String (string_of_datatype d))] in
	function
		SBlock sl        			-> `Assoc [("sblock", `List (List.map (map_sstmt_to_json) sl))]
	|   SExpr(e, d)          		-> `Assoc [("sexpr", `Assoc ([("expr", map_sexpr_to_json e)] @ (datatype d)))]
	|   SReturn(e, d)    			-> `Assoc [("sreturn", `Assoc ([("return", map_sexpr_to_json e)] @ (datatype d)))]
	|   SIf (e, s1, s2)       		-> `Assoc [("sif", `Assoc [("cond", map_sexpr_to_json e); ("ifbody", map_sstmt_to_json s1)]); ("selse", map_sstmt_to_json s2)]
	|   SFor (e1, e2, e3, s)  		-> `Assoc [("sfor", `Assoc [("init", map_sexpr_to_json e1); ("cond", map_sexpr_to_json e2); ("inc", map_sexpr_to_json e3); ("body", map_sstmt_to_json s)])]
	|   SWhile (e, s)    			-> `Assoc [("swhile", `Assoc [("cond", map_sexpr_to_json e); ("body", map_sstmt_to_json s)])]
	|   SBreak           			-> `String "sbreak"
	|   SContinue        			-> `String "scontinue"
	|   SLocal(d, s, e)  			-> `Assoc [("slocal", `Assoc [("datatype", `String (string_of_datatype d)); ("name", `String s); ("val", map_sexpr_to_json e)])]

let string_of_func_type = function
	User -> "user" | Reserved -> "reserved"

let map_sfdecl_to_json sfdecl =
	`Assoc[("sfdecl", `Assoc[
		("sfname", `String (string_of_fname sfdecl.sfname));
		("sreturnType", `String (string_of_datatype sfdecl.sreturnType));
		("sformals", map_formals_to_json sfdecl.sformals);
		("sbody", `List (List.map (map_sstmt_to_json) sfdecl.sbody));
		("func_type", `String(string_of_func_type sfdecl.func_type));
	])] 

let map_sfdecls_to_json sfdecls =
	`List(List.map map_sfdecl_to_json sfdecls)

let map_scdecls_to_json scdecls = 		
	`List(List.map (fun scdecl -> 
						`Assoc [("scdecl", 
							`Assoc[
								("scname", `String scdecl.scname); 
								("sfields", map_fields_to_json scdecl.sfields);
								("sfuncs", map_sfdecls_to_json scdecl.sfuncs);
							])
						]) 
		scdecls)

let map_sprogram_to_json sprogram = 
	`Assoc [("sprogram", `Assoc [
		("classes", map_scdecls_to_json sprogram.classes);
		("functions", map_sfdecls_to_json sprogram.functions);
		("main", map_sfdecl_to_json sprogram.main);
		("reserved", map_sfdecls_to_json sprogram.reserved);
	])]

(* Print tokens *)

let string_of_token = function
		LPAREN				-> "LPAREN"	
	| 	RPAREN				-> "RPAREN"	
	| 	LBRACE				-> "LBRACE"	
	| 	RBRACE				-> "RBRACE"	
	| 	SEMI				-> "SEMI"	
	| 	COMMA				-> "COMMA"	
	| 	PLUS				-> "PLUS"	
	| 	MINUS				-> "MINUS"	
	| 	TIMES				-> "TIMES"	
	| 	DIVIDE				-> "DIVIDE"	
	| 	ASSIGN				-> "ASSIGN"	
	| 	EQ					-> "EQ"
	| 	NEQ					-> "NEQ"
	| 	LT					-> "LT"
	| 	LEQ					-> "LEQ"
	| 	GT					-> "GT"
	| 	GEQ					-> "GEQ"
	| 	AND					-> "AND"
	| 	OR					-> "OR"
	| 	NOT					-> "NOT"
	| 	DOT					-> "DOT"
	| 	LBRACKET			-> "LBRACKET"		
	| 	RBRACKET			-> "RBRACKET"		
	| 	BAR					-> "BAR"
	| 	IF					-> "IF"
	| 	ELSE				-> "ELSE"	
	| 	FOR					-> "FOR"
	| 	WHILE				-> "WHILE"	
	| 	RETURN				-> "RETURN"	
	| 	INT					-> "INT"
	| 	FLOAT				-> "FLOAT"	
	| 	BOOL				-> "BOOL"	
	| 	CHAR				-> "CHAR"	
	| 	VOID				-> "VOID"	
	| 	NULL				-> "NULL"	
	| 	TRUE				-> "TRUE"	
	| 	FALSE				-> "FALSE"	
	| 	CLASS				-> "CLASS"	
	| 	CONSTRUCTOR			-> "CONSTRUCTOR"		
	| 	PUBLIC				-> "PUBLIC"	
	| 	PRIVATE				-> "PRIVATE"	
	| 	EXTENDS				-> "EXTENDS"	
	| 	INCLUDE				-> "INCLUDE"	
	| 	THIS				-> "THIS"	
	| 	BREAK				-> "BREAK"	
	| 	CONTINUE			-> "CONTINUE"	
	|   NEW 				-> "NEW"	
	| 	INT_LITERAL(i)		-> "INT_LITERAL(" ^ string_of_int i ^ ")"
	| 	FLOAT_LITERAL(f)	-> "FLOAT_LITERAL(" ^ string_of_float f ^ ")"
	| 	CHAR_LITERAL(c)		-> "CHAR_LITERAL(" ^ Char.escaped c ^ ")"
	| 	STRING_LITERAL(s)	-> "STRING_LITERAL(" ^ s ^ ")"
	| 	ID(s)				-> "ID(" ^ s ^ ")"
	| 	DELETE 				-> "DELETE"
	| 	MODULO 				-> "MODULO"
	|  	EOF					-> "EOF"

let string_of_token_no_id = function
		LPAREN				-> "LPAREN"	
	| 	RPAREN				-> "RPAREN"	
	| 	LBRACE				-> "LBRACE"	
	| 	RBRACE				-> "RBRACE"	
	| 	SEMI				-> "SEMI"	
	| 	COMMA				-> "COMMA"	
	| 	PLUS				-> "PLUS"	
	| 	MINUS				-> "MINUS"	
	| 	TIMES				-> "TIMES"	
	| 	DIVIDE				-> "DIVIDE"	
	| 	ASSIGN				-> "ASSIGN"	
	| 	EQ					-> "EQ"
	| 	NEQ					-> "NEQ"
	| 	LT					-> "LT"
	| 	LEQ					-> "LEQ"
	| 	GT					-> "GT"
	| 	GEQ					-> "GEQ"
	| 	AND					-> "AND"
	| 	OR					-> "OR"
	| 	NOT					-> "NOT"
	| 	DOT					-> "DOT"
	| 	LBRACKET			-> "LBRACKET"		
	| 	RBRACKET			-> "RBRACKET"		
	| 	BAR					-> "BAR"
	| 	IF					-> "IF"
	| 	ELSE				-> "ELSE"	
	| 	FOR					-> "FOR"
	| 	WHILE				-> "WHILE"	
	| 	RETURN				-> "RETURN"	
	| 	INT					-> "INT"
	| 	FLOAT				-> "FLOAT"	
	| 	BOOL				-> "BOOL"	
	| 	CHAR				-> "CHAR"	
	| 	VOID				-> "VOID"	
	| 	NULL				-> "NULL"	
	| 	TRUE				-> "TRUE"	
	| 	FALSE				-> "FALSE"	
	| 	CLASS				-> "CLASS"	
	| 	CONSTRUCTOR			-> "CONSTRUCTOR"		
	| 	PUBLIC				-> "PUBLIC"	
	| 	PRIVATE				-> "PRIVATE"	
	| 	EXTENDS				-> "EXTENDS"	
	| 	INCLUDE				-> "INCLUDE"	
	| 	THIS				-> "THIS"	
	| 	BREAK				-> "BREAK"	
	| 	CONTINUE			-> "CONTINUE"	
	|   NEW 				-> "NEW"		
	| 	INT_LITERAL(i)		-> "INT_LITERAL"
	| 	FLOAT_LITERAL(f)	-> "FLOAT_LITERAL"
	| 	CHAR_LITERAL(c)		-> "CHAR_LITERAL"
	| 	STRING_LITERAL(s)	-> "STRING_LITERAL"
	| 	ID(s)				-> "ID"
	| 	DELETE 				-> "DELETE"
	| 	MODULO 				-> "MODULO"
	|  	EOF					-> "EOF"

let token_list_to_string_endl token_list =
  let rec helper last_line_number = function
		(token, curr)::tail ->
		let line = curr.lineno in 
		(if line != last_line_number then "\n" ^ string_of_int line ^ ". " else " ") ^
		string_of_token token ^ helper line tail
	| 	[] -> "\n"
  in helper 0 token_list

let token_list_to_string token_list =
  let rec helper = function
		(token, line)::tail ->
		string_of_token_no_id token ^ " " ^ helper tail
	| 	[] -> "\n"
  in helper token_list