% 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}[2023/09/16 0.1.0 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, order)
	
	print("\n\ntruthtable v0.1.0\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

	tex.print(dHeader);

	
	local startVal;
	local endVal;
	local stepVal;

	if order == "asc" then
		startVal = 0;
		endVal = rows - 1;
		stepVal = 1;
	else 
		startVal = rows - 1;
		endVal = 0;
		stepVal = -1;
	end

	for i = startVal,endVal,stepVal
	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) .. [[\\]];

		tex.print(row);
	end
end

\end{luacode*}

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

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

\endinput