Generate LaTeX Truth Table with Python Cheetah

Not really an answer to this question as it doesn't involve Cheetah. However if anyone searches for truth table, potentially they would like a truth table macro so I'll post this for that reason. The macro \truthtable{<prims>}{<exprs>} (compile with LuaLaTeX) takes two arguments:

  • The first argument is a comma separated list of primitives (variables)
  • The second argument is a semi-colon separated list of logical statements involving the operations of:
    • Negation, entered as -
    • Conjunction, entered as *
    • Disjunction, entered as +
    • Implication, entered as (stuff)->(stuff)

For example, \truthtable{a,b,c,d}{-a;a*b*d;(a)->(b);a*(b+c);(a*(b+c))->(b)} produces the following:

enter image description here

\documentclass{article}
\usepackage{xparse}

\begingroup
  \catcode`\%=12\relax
  \gdef\patmatch{"(%b())->(%b())","!%1||%2"}
\endgroup

\def\setimpaux#1{%
  \directlua{
    local s, _ = string.gsub("\luatexluaescapestring{#1}",\patmatch)
    tex.sprint(s)
  }
}

\ExplSyntaxOn
\int_new:N \l__tt_num_rows_int
\int_new:N \l__tt_num_cols_int
\int_new:N \l__tt_num_vars_int
\clist_new:N \l__tt_vars_clist
\seq_new:N \l__tt_exprs_seq
\seq_new:N \l__tt_header_seq

\NewDocumentCommand {\truthtable}{ m m }
    {
        \truth_table:nn {#1}{#2}
    }

\cs_new_protected:Npn \truth_table:nn #1#2
    {
        \clist_set:Nn \l__tt_vars_clist {#1}
        \seq_set_split:Nnn \l__tt_exprs_seq {;} {#2}
        \int_set:Nn \l__tt_num_vars_int {\clist_count:N \l__tt_vars_clist}
        \int_set:Nn \l__tt_num_rows_int {\fp_to_int:n {2^{\l__tt_num_vars_int}-1}}
        \int_set:Nn \l__tt_num_cols_int {\clist_count:N \l__tt_vars_clist +\seq_count:N \l__tt_exprs_seq}
        \__tt_gen_bins:
        \seq_map_function:NN \l__tt_exprs_seq \__tt_eval_bools:n
        \__tt_build_table:
    }

\cs_new_protected:Npn \__tt_build_header:
    {
        \seq_set_from_clist:NN \l__tt_header_seq \l__tt_vars_clist
        \seq_concat:NNN \l__tt_header_seq \l__tt_header_seq \l__tt_exprs_seq
        \tl_set:Nx \l_tmpa_tl {\seq_use:Nnnn \l__tt_header_seq {&}{&}{&}}
        \tl_replace_all:Nnn \l_tmpa_tl {*} {\wedge}
        \tl_replace_all:Nnn \l_tmpa_tl {+} {\vee}
        \tl_replace_all:Nnn \l_tmpa_tl {->} {\to}
        \tl_replace_all:Nnn \l_tmpa_tl {-} {\neg}
        \tl_use:N \l_tmpa_tl
    }

\cs_generate_variant:Nn \seq_use:Nnnn {cnnn}
\cs_new_protected:Npn \__tt_build_table:
    {
        \begin{array}{*{\int_use:N \l__tt_num_cols_int}{c}}
            \__tt_build_header:\\\hline
            \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
                {
                    \seq_use:cnnn {l__tt_row_{##1}_seq}{&}{&}{&}\\
                }
        \end{array}
    }

\cs_new_protected:Npn \__tt_set_imp:n #1
    {
        \tl_if_in:nnT {#1} {->}
            {
                \tl_set:Nx \l_tmpb_tl {\setimpaux{#1}}
                \exp_args:NV \__tt_set_imp:n \l_tmpb_tl
            }
    }
\cs_generate_variant:Nn \__tt_set_imp:n {V}

\cs_generate_variant:Nn \tl_replace_all:Nnn {Nnx}
\cs_new_protected:Npn \__tt_eval_bools:n #1
    {
        \tl_set:Nn \l_tmpa_tl {#1}
        \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
            {
                \int_set:Nn \l_tmpa_int {1}
                \tl_set_eq:NN \l_tmpb_tl \l_tmpa_tl
                \__tt_set_imp:V \l_tmpb_tl
                \tl_replace_all:Nnn \l_tmpb_tl {*}{&&}
                \tl_replace_all:Nnn \l_tmpb_tl {+}{||}
                \tl_replace_all:Nnn \l_tmpb_tl {-}{!}
                \clist_map_inline:Nn \l__tt_vars_clist
                    {
                        \tl_replace_all:Nnx \l_tmpb_tl {####1} {\seq_item:cn {l__tt_row_{##1}_seq} {\l_tmpa_int}}
                        \int_incr:N \l_tmpa_int
                    }
                \seq_put_right:cx {l__tt_row_{##1}_seq} {\fp_eval:n \l_tmpb_tl}
            }
    }

\cs_generate_variant:Nn \seq_set_split:Nnn {cnx}
\cs_new_protected:Npn \__tt_gen_bins:
    {
        \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
            {
                \seq_clear_new:c {l__tt_row_{##1}_seq}
                \seq_set_split:cnx {l__tt_row_{##1}_seq} {} {\int_to_binary:n {##1}}
                \int_while_do:nn {\seq_count:c {l__tt_row_{##1}_seq} < \l__tt_num_vars_int}
                    {
                        \seq_put_left:cn {l__tt_row_{##1}_seq} {0}
                    }
            }
    }

\ExplSyntaxOff
\begin{document}

\[
\truthtable{a,b,c,d}{-a;a*b*d;(a)->(b);a*(b+c);(a*(b+c))->(b)}
\]

\end{document}

I am also interested in generating LaTeX code with Python, so your posting gave me the hint to the framework to use. I just created a simple example:

from Cheetah.Template import Template
definition = """\\documentclass{article}

\\title{$paper.title}
\\author{$author.name}

\\begin{document}

\\maketitle

\\end{document}"""

class author:
    """A simple example author class"""
    name = "Uwe Ziegenhagen"
    def f(self):
        return 'Hello World'

class paper:
    """A simple example paper class"""
    title = "My First paper"
    def f(self):
        return 'hello world'

uwe = author()
mypaper = paper()

print Template(definition, searchList=[{'author' : uwe,'paper' : mypaper}])

For archiving purposes, for people less familiar with Cheetah framework, the above is just a regular Python script. The output of the script on the standard output (could be of course piped into a file) is the LaTeX code.


Template modules, like Cheetah, are overkill if you want to use Python for this type of work. I would use straight Python as follows:

template = """\\documentclass{{article}}

\\title{{ {title} }}
\\author{{ {author} }}

\\begin{{document}}

\\maketitle

\\end{{document}}"""

info = {"title": "My First Paper",
        "author": "A. Author"}

print template.format(**info)