/*
 *  Neko Compiler
 *  Copyright (c)2005 Motion-Twin
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License or the LICENSE file for more details.
 */

var dir = &None;
var verbose = &false;

function out(file,ext) {
	var file = Sys.without_extension file + ext;
	match *dir {
	| None -> file
	| Some dir -> dir + Sys.without_dir file
	}
}

function is_nxml(str) {
	var len = String.length str;
	if len <= 10 || String.sub str 0 6 != "<nxml>" then
		None
	else if String.sub str (len - 7) 7 == "</nxml>" then
		Some (String.sub str 6 (len - 13))
	else if String.sub str (len - 8) 8 == "</nxml>\n" then
		Some (String.sub str 6 (len - 14))
	else if String.sub str (len - 9) 9 == "</nxml>\r\n" then
		Some (String.sub str 6 (len - 15))
	else
		None
}

function parse_multiformat(file) {
	var str = IO.file_contents file;
	match is_nxml str {
	| Some nxml ->
		Neko.Xml.parse nxml Lexer.null_pos
	| None when String.length str > 4 && String.sub str 0 4 == Neko.Binast.header ->
		Neko.Binast.parse (IO.read_string str) Lexer.null_pos
	| None ->
		var i = IO.read_string str;
		var lex = Lexer.create Buffer.create();
		Lexer.input lex file i 1 0;
		Neko.Parser.parse lex;
	}
}

function compile(file) {
	if *verbose then printf "Compiling %s\n" file;
	var ast = parse_multiformat file;
	var code = Neko.Compile.compile ast;
	var o = IO.write_file out(file,".n") true;
	Neko.Bytecode.write o code;
	IO.close_out o
}

function dump(file) {
	if *verbose then printf "Dumping %s\n" file;
	var i = IO.read_file file true;
	var code = Neko.Bytecode.read i;
	IO.close_in i;
	var o = IO.write_file out(file,".dump") false;
	Neko.Bytecode.dump o code;
	IO.close_out o
}

function print_ast(file) {
	if *verbose then printf "Printing %s\n" file;
	var ast = parse_multiformat file;
	var o = IO.write_file out(file,"2.neko") false;
	Neko.Printer.print Neko.Printer.create(o) ast;
	IO.close_out o
}

function release(file) {
	if *verbose then printf "Releasing %s\n" file;
	var i = IO.read_file file true;
	var globals, code = Neko.Bytecode.read i;
	IO.close_in i;
	var globals = Array.map (function(g) {
		match g {
		| Neko.Bytecode.GlobalVar _
		| Neko.Bytecode.GlobalDebug _ -> Neko.Bytecode.GlobalVar ""
		| _ -> g
		}
	}) globals;
	var o = IO.write_file out(file,".n") true;
	Neko.Bytecode.write o (globals,code);
	IO.close_out o
}

function documentation(file) {
	if *verbose then printf "Building documentation for %s\n" file;
	var i = IO.read_file file false;
	var lex = Lexer.create Neko.Doc.status();
	Lexer.input lex file i 1 0;
	var doc = Neko.Doc.parse lex;
	IO.close_in i;
	var o = IO.write_file out(file,".html") false;
	Neko.Doc.to_html o doc;
	IO.close_out o
}

function report(msg,p) {
	if p == Lexer.null_pos then	{
		Stack.dump IO.stderr Stack.exc();
		IO.printf IO.stderr "Exception : %s\n" msg
	} else
		IO.printf IO.stderr "%s(%d): %s\n" (Lexer.source p,Lexer.line p,msg);
	Sys.exit(-1);
}

function complete(dir) {
	var l = String.length dir;
	if l > 0 && String.get dir (l - 1) != '/' && String.get dir (l - 1) != '\\' then
		dir + "/"
	else
		dir
}

try {
	var v = Sys.version;
	var head = "Neko Compiler v" + v.maj + "." + v.min + "." + v.build + " - (c)2005-2007 Motion-Twin\n Usage : neko [options] files...";
	var link = &None;
	var links = &[];
	var decl = [
		("-d", Args.String (function(f) { dump f }) , "<file> : dump bytecode");
		("-z", Args.String (function(f) { release f }), "<file> : make bytecode release");
		("-p", Args.String (function(f) { print_ast f }), "<file> : parse and print neko source");
		("-doc", Args.String (function(f) { documentation f }) , "<file> : make documentation");
		("-o", Args.String (function(f) { dir := Some (complete f) }),"<dir> : set output directory");
		("-console", Args.Void (function() { Neko.Console.run() }),": run the console");
		("-link", Args.String (function(f) { link := Some f }),"<file> : link bytecodes files");
		("-v", Args.Void (function() { verbose := true }) , ": verbose mode");
	];
	Args.parse head decl (function(f) {
		match *link {
		| None -> compile f
		| Some _ -> links := f :: *links
		}
	});
	match *link { None -> () | Some f -> Neko.Linker.link f List.rev(*links) };
} catch {
	| Neko.Lexer.Error(msg,pos) -> report Neko.Lexer.error_msg(msg) pos
	| Neko.Parser.Error(msg,pos) -> report Neko.Parser.error_msg(msg) pos
	| Neko.Compile.Error(msg,pos) -> report Neko.Compile.error_msg(msg) pos
	| Neko.Doc.Error(msg,pos) -> report Neko.Doc.error_msg(msg) pos
	| e -> report string(e) Lexer.null_pos
}
