#let truth-table(outputs: (), inputs: none) = { let variables = calc.max(..outputs.map(output => calc.ceil(calc.log(output.at(1).len()) / calc.log(2)))) if inputs == none { inputs = ($a$, $b$, $c$, $d$).slice(0, variables) } assert(outputs.len() >= 1, message: "There has to be at least one output") assert(inputs.len() == variables, message: "There aren't enough variables to label") let num-to-bin(x, digits) = { let bits = () while x != 0 { bits.push(calc.rem(x, 2)) x = calc.floor(x / 2) } range(digits).map(x => bits.at(digits - x - 1, default: 0)) } table( columns: (auto,) * (variables + outputs.len()), stroke: (x, y) => ( left: if x == variables { 1pt } else if x > 0 { 0.5pt }, top: if y == 1 { 1pt } else if y > 0 { 0.5pt }, ), inset: 4pt, if inputs != none { table.header(..inputs.map(x => [#x]), ..outputs.map(((x, _)) => [#x])) }, ..range(calc.pow(2, variables)) .map(x => (..num-to-bin(x, variables).map(y => [#y]), ..outputs.map(((_, y)) => [#y.at(x, default: [])]))) .flatten() ) }