% truthtable.sty
%% Copyright 2021 D. Flück
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
%   http://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
% This work has the LPPL maintenance status “author-maintained”.
% 
% The Current Maintainer of this work is D. Flück.
% 
% This work consists of the file truthtable.sty.
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{truthtable}[2021/10/08 0.0.2 Package for generating truth tables automatically using LuaTeX]

\ProcessOptions\relax
\@ifpackageloaded{luacode}{
	\PackageWarningNoLine{truthtable}{Package luacode was already loaded}
}{
	\RequirePackage{luacode}
}

\begin{luacode*}

function Impl(a,b)
	return (not a or b);
end

function Equiv(a,b)
	return ((a and b) or ((not a) and (not b)));
end

function Xor(a,b)
	return ((a or b) and (not (a and b)));
end

function Nand(a,b)
	return (not (a and b));
end

function ComputeRows(header)
	return 2^header
end

function Split(s, delimiter)
    local result = {};
    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
        table.insert(result, match);
    end
    return result;
end

function EvaluateFormula(formula)

	local parsedFormula = "function res() return( " .. string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(formula, " ", ""),">>","Impl"),"__","Equiv"),"<>","Equiv"),"%^","Xor"),"!&","Nand"),"!","not "),"&" ," and "),"|"," or "),";",",") .. " ) end";

	chunk = load(parsedFormula);
	chunk();
	local result =  res();
	return result;
end

function toBits(num)
    local t = "" -- will contain the bits
    while num>0 do
        local rest = math.fmod(num,2)
        if (rest == 1) then
			t =  "1" .. t
		else
			t = "0" .. t 
		end
		
        num=(num-rest)/2
    end
    return t;
end

function printTruthValue(expr, dTrue, dFalse)

	local returnVal = ""

	if (expr) then
		returnVal = dTrue;
	else
		returnVal = dFalse;
	end

	return returnVal;
end

function parse(commaSepVariables, commaSepDisplayVariables, commaSepResultRows, commaSepResultDisplayRows, displayTrue, displayFalse)
	
	print("\n\ntruthtable v0.0.2\n")

	local vrbls = Split(commaSepVariables, ",");
	local numberOfColumns = #(vrbls);
	local rows = ComputeRows(numberOfColumns);
	local dVrbls = Split(commaSepDisplayVariables, ",");
	local resRows = Split(commaSepResultRows, ",");
	local dResRows = Split(commaSepResultDisplayRows, ",");

	local dHeader = string.gsub(commaSepDisplayVariables, ",", " & ") .. " & " .. string.gsub(commaSepResultDisplayRows, ",", " & ") .. " \\\\ \\hline";

	if (#(dVrbls) ~= #(vrbls)) then
		print("Error: The number of variables does not match the number of display variables.");
		return
	end

	if (#(dResRows) ~= #(resRows)) then
		print("Error: The number of statements does not match the number of display statements.");
		return
	end

	local tableContent = dHeader;

	for i = (rows - 1),0,-1
	do
		local bitString = toBits(i);

		while #bitString < numberOfColumns do
			bitString = "0" .. bitString
		end

		local wVrbls = commaSepVariables;
		local wCommaSepRows = commaSepResultRows
		for ii = 1,numberOfColumns
		do
			wVrbls = string.gsub(wVrbls, vrbls[ii], (string.sub(bitString,ii,ii) == "1" ) and "+" or "-" )
			wCommaSepRows = string.gsub(wCommaSepRows, vrbls[ii], (string.sub(bitString,ii,ii) == "1" ) and "+" or "-" )
		end

		local aWVrbls = Split(string.gsub(string.gsub(wVrbls, "+", "true"),"-", "false"), ",");

		local aWCommaSepRows = Split(string.gsub(string.gsub(wCommaSepRows, "+", "true"),"-", "false"), ",");

		local row = "";

		for c = 1,#(aWVrbls)
		do
			row = row .. printTruthValue(EvaluateFormula(aWVrbls[c]), displayTrue, displayFalse) .. " & ";
		end

		for c = 1,#(aWCommaSepRows)
		do
			row = row .. printTruthValue(EvaluateFormula(aWCommaSepRows[c]), displayTrue, displayFalse) .. " & ";
		end

		row = string.sub(row, 1, #row - 2) .. "\\\\"

		tableContent = tableContent .. "\n" .. row
	end

	tex.print(tableContent);
end

\end{luacode*}

\newcommand{\truthtable}[6]{
	\luadirect{parse("#1", "\luaescapestring{#2}", "\luaescapestring{#3}", "\luaescapestring{#4}", "\luaescapestring{#5}","\luaescapestring{#6}")}
}

\endinput