A truth table is a display of the inputs to, and the output of a Boolean function organized as a table where each row gives one combination of input values and the corresponding value of the function.



Task

 Input a Boolean function from the user as a string then calculate and print a formatted truth table for the given function.
 (One can assume that the user input is correct).
 Print and show output for Boolean functions of two and three input variables, but any program should not be limited to that many variables in the function. 
 Either reverse-polish or infix notation expressions are allowed.


Related tasks

   Boolean values
   Ternary logic


See also

   Wolfram MathWorld entry on truth tables.
   some "truth table" examples from Google.


import "/dynamic" for Struct
import "/ioutil" for Input
import "/seq" for Stack
import "/str" for Str
 
var Variable = Struct.create("Variable", ["name", "value"])
 
// use integer constants as bools don't support bitwise operators
var FALSE = 0
var TRUE = 1
 
var expr = ""
var variables = []
 
var isOperator = Fn.new { |op| "&|!^".contains(op) }
 
var isVariable = Fn.new { |s| variables.map { |v| v.name }.contains(s) }
 
var evalExpression = Fn.new {
    var stack = Stack.new()
    for (e in expr) {
        var v
        if (e == "T") {
            v = TRUE
        } else if (e == "F") {
            v = FALSE
        } else if (isVariable.call(e)) {
            var vs = variables.where { |v| v.name == e }.toList
            if (vs.count != 1) Fiber.abort("Can only be one variable with name %(e).")
            v = vs[0].value
        } else if (e == "&") {
            v = stack.pop() & stack.pop()
        } else if (e == "|") {
            v = stack.pop() | stack.pop()
        } else if (e == "!") {
            v = (stack.pop() == TRUE) ? FALSE : TRUE
        } else if (e == "^") {
            v = stack.pop() ^ stack.pop()
        } else {
            Fiber.abort("Non-conformant character %(e) in expression")
        }
        stack.push(v)
    }
    if (stack.count != 1) Fiber.abort("Something went wrong!")
    return stack.peek()
}
 
var setVariables // recursive
setVariables = Fn.new { |pos|
    var vc = variables.count
    if (pos > vc) Fiber.abort("Argument cannot exceed %(vc).")
    if (pos == vc) {
        var vs = variables.map { |v| (v.value == TRUE) ? "T" : "F" }.toList
        var es = (evalExpression.call() == TRUE) ? "T" : "F"
        System.print("%(vs.join("  "))  %(es)")
        return
    }
    variables[pos].value = FALSE
    setVariables.call(pos + 1)
    variables[pos].value = TRUE
    setVariables.call(pos + 1)
}
 
System.print("Accepts single-character variables (except for 'T' and 'F',")
System.print("which specify explicit true or false values), postfix, with")
System.print("&|!^ for and, or, not, xor, respectively; optionally")
System.print("seperated by spaces or tabs. Just enter nothing to quit.")
 
while (true) {
    expr = Input.text("\nBoolean expression: ")
    if (expr == "") return
    expr = Str.upper(expr).replace(" ", "").replace("\t", "")
    variables.clear()
    for (e in expr) {
        if (!isOperator.call(e) && !"TF".contains(e) && !isVariable.call(e)) {
            variables.add(Variable.new(e, FALSE))
        }
    }
    if (variables.isEmpty) return
    var vs = variables.map { |v| v.name }.join("  ")
    System.print("\n%(vs)  %(expr)")
    var h = vs.count + expr.count + 2
    System.print("=" * h)
    setVariables.call(0)
}