/*
	MakeFunctionPlugin() - create an external plugin for a numerical function
	Version 0.1
	Requires: compiler named "c++" with ELF .so support; Yacas headers in FindFile("")/include; current directory must be writable
	Usage: MakeFunctionPlugin(ExtName, ArgList, Body)
*/

MakeArgumentList(argList, padding) := [ /* may assume to have a nonempty list */
	Local(item, result);
	/* build a list and add commas after 1st element */
	result := padding : String(Head(argList));
	ForEach(item, Tail(argList))
	  result := result : "," : padding : String(item);
	result;
];

MakeFunctionPrototype(name, argList) := [
	"double " : name : "(" : MakeArgumentList(argList, "double ") : ")";
];

MakeFunctionPlugin(extName, fBody) := [
	Local(dirBase, fileBase, intName, exportName, includeDir, dllName, dllPath, commandLine, argList);
	/* check argument type */
	argList := VarList(fBody);
	Check(IsString(extName) And Length(argList) > 0,
	  "Error in MakeFunctionPlugin: " : extName
	  : " must be a string and " : argList : " a nonempty list.");
	includeDir := FindFile("include/");
	Check(includeDir != "",
	  "Error in MakeFunctionPlugin: no include/ under " : FindFile(""));
	/* determine file names */
	dirBase := "plugins.tmp/";	// where all plugin files will be kept
	SystemCall("test -d " : dirBase : "|| mkdir -p " : dirBase);
	fileBase := dirBase : extName : "_plugin";
	intName := extName : "_plugin_cc";	// name of C++ function
	exportName := extName : "_plugin"; // name of intermediate Yacas function
	dllName := "lib" : intName : ".so";
	dllPath := dirBase : dllName;
	/* specify more include dirs to be able to compile from different places */
	commandLine := "c++ -shared -I. -I.. -I" : includeDir : " -I" : includeDir : "plat/linux32 -Wl,-soname," : dllName : " -o " : dllPath : " " : fileBase : ".cc " : fileBase : "_api.cc >& " : fileBase : ".log";
	/* write C++ header */
	ToFile(fileBase : ".h")
	  WriteString(
		"// GENERATED FILE: " : fileBase : ".h" : Nl()
		: MakeFunctionPrototype(intName, argList) : ";" : Nl()
	  );
	/* write C++ body */
	ToFile(fileBase : ".cc")
	  WriteString(
		"// GENERATED FILE: " : fileBase : ".cc" : Nl()
		: "#include \"stubs.h\"" : Nl()
		: "#include \"" : fileBase : ".h\"": Nl()
		: "#include <math.h>" : Nl()
		: "const double Pi=" : CForm(Hold(4.*ArcTan(1.))): ";" : Nl()
		: MakeFunctionPrototype(intName, argList) : " {" : Nl()
		: "return " : CForm(fBody) : ";" : Nl()
		: "}" : Nl()
	  );
	/* write Yacas stub */
	ToFile(fileBase : ".stub") [
	  WriteString(
	    "/* GENERATED FILE: " : fileBase : ".stub */" : Nl()
	    : "Use(\"cstubgen.rep/code.ys\"); StubApiCStart();" : Nl()
	    : "StubApiCInclude(\"\\\"" : fileBase : ".h\\\"\");" : Nl()
	    : "StubApiCFunction(\"double\", \"" : intName : "\", \"" : exportName : "\",");
	  Write(FillList("double", Length(argList)));
	  WriteString(
	    ");" : Nl()
	    : "StubApiCFile(\"" : fileBase : "_api\");" : Nl()
	  );
	];
	/* generate C++ stub */
	Load(fileBase : ".stub");
	/* compile plugin for Linux */
	ToFile(fileBase : ".compile") WriteString(commandLine : Nl());
	/* delete old file */
	SystemCall("rm -f " : dllPath);
	SystemCall(commandLine);
	/* If compilation succeeds, DLL file is present */
	Check(FindFile(dllPath) = dllPath, "Error in MakeFunctionPlugin: compilation of " : fileBase : ".so failed.");
	/* Load DLL file */
	DllUnload(dllPath);
	DllLoad(dllPath);
	Echo({"Function " : extName : "(" : MakeArgumentList(argList, ""): ") loaded from " : dllPath});
	/* define wrapper */
	NFunction(extName, exportName, argList);
];

/* Example code to read the PID of the Yacas process into the variable PID
	We have to jump through the hoops here because we can't redirect STDOUT to a Yacas stream... ideally it would be just this:
	PID := FromString(ToString() SystemCall("echo $PPID ';'")) Read();
*/
GetYacasPID() := [
	SystemCall("echo $PPID ';' > /tmp/yacas-tmp");
	FromFile("/tmp/yacas-tmp") Read();
];

/* show a Yacas expression graphically in a PS file */
/* The following global variable will define the available PS viewer */
If(Not(IsBound(PSViewCommand)), PSViewCommand := "gv");

ShowPS(expr) := [
	/* Create a temporary file */
	SystemCall("echo \\\"/tmp/yacas-tmpfile-$$\\\" ';' > /tmp/yacas-tmp");
	filename := FromFile("/tmp/yacas-tmp") Read();
	SystemCall("rm -f /tmp/yacas-tmp");
	ToFile(filename : ".tex") WriteString(
"\\documentclass{article} \\begin{document} \\thispagestyle{empty}
" : TeXForm(expr) : "
\\end{document}
");
	WriteString("Expression exported as " : filename : ".tex" : Nl());
	/* Make PS file */
	SystemCall("cd /tmp; latex " : filename : " > /dev/null; dvips -q -o " : filename : ".ps " : filename : ".dvi");
	/* Show PS file */
	SystemCall(PSViewCommand : " " : filename : ".ps; rm -f " : filename : ".*");
];
