This page contains a program that will convert a formula of truth-functional logic into an equivalent formula that uses only the Sheffer Stroke (representing the NAND operation). It won't generally be the shortest equivalent formula, though! Try out ~(A&B), for example. You can generate truth tables for the converted formulas here. See below for the keyboard symbols to use for the logical connectives.
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
Wittgenstein famously used the Sheffer stroke in the Tractatus, but there it represents the NOR operation (rather than NAND). So here is another converter, this time one that will output an equivalent formula using only a NOR-stroke (here represented using '!'). So (A!B) means ~(AvB) or, equivalently, (~A&~B) i.e. the joint denial of A and B. In the Tractatus Wittgenstein generalized this into his N operator, which forms the joint denial of any collection of propositions. You can read more about that here.
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
Symbols: use the following keyboard symbols in your input for the various logical connectives:
234 |
235 |
236 |
237 |
~
for negation
238 |
&
for conjunction
239 |
v
for disjunction
240 |
>
for the conditional
241 |
< >
for the biconditional
242 |
#
for absurdity
243 |
|
for NAND (aka the Sheffer Stroke)
244 |
!
for NOR (Wittgenstein's Sheffer Stroke)
245 |
246 |
247 |
248 |
Here are some examples of well-formed inputs the program will accept:
249 |
250 |
251 |
~A
252 |
(A & B)
253 |
(A & (~B > C))
254 |
(# > (B v ~A))
255 |
(A|B) <> ~(A & B)
256 |
~((A v D) <> (B & C))
257 |
258 |
259 |
260 |
The source code is available on GitHub, just click the icon at the top right corner of the page.
261 |
262 |
263 |
264 |
This page contains a program that will generate truth tables for formulas of propositional logic. You can enter multiple formulas separated by commas to include more than one formula in a single table (e.g. to test for entailment). Below you can select which symbols to use for the truth-values and connectives, the style of the table (full table, main connective only, plain text, or LaTeX output), and (by special request!) two-valued or different types of three-valued tables.
Here are some examples of well-formed inputs the program will accept:
237 |
238 |
239 |
~A
240 |
(A & B)
241 |
(A & (~B > C))
242 |
(# > (B v ~A))
243 |
(A|B) <> ~(A & B)
244 |
(A <> (B v C)), A, (~B > C)
245 |
246 |
247 |
The source code is available on GitHub, just click the icon at the top right corner of the page.
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
268 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
--------------------------------------------------------------------------------
/truthtable.js:
--------------------------------------------------------------------------------
1 | // Truth Table Generator
2 | //
3 | // The MIT License (MIT)
4 | //
5 | // Copyright (c) 2010-2024 Michael Rieppel
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | /*************************************************************************************/
25 |
26 | function htmlchar(c,tv,cs) {
27 | switch(c) {
28 | case true : // return char based on selected truth value style
29 | switch(tv) {
30 | case 'tb': return '⊤';
31 | case 'tf': return 'T';
32 | case 'oz': return '1';
33 | }
34 | case false : // return char based on selected truth value style
35 | switch(tv) {
36 | case 'tb': return '⊥';
37 | case 'tf': return 'F';
38 | case 'oz': return '0';
39 | }
40 | case null : // return char based on selected truth value style (third truth value)
41 | switch(tv) {
42 | case 'tb': return 'N';
43 | case 'tf': return 'N';
44 | case 'oz': return 'N';
45 | }
46 | case '~' : // return connective chars based on selected cset
47 | switch(cs) {
48 | case 'cs1': return '¬';
49 | default : return '~';
50 | }
51 | case '&' :
52 | switch(cs) {
53 | case 'cs1' : return '∧';
54 | default : return '&';
55 | }
56 | case 'v' : return '∨';
57 | case '>' :
58 | switch(cs) {
59 | case 'cs3' : return '⊃';
60 | default : return '→';
61 | }
62 | case '<>' :
63 | switch(cs) {
64 | case 'cs3' : return '≡';
65 | default : return '↔';
66 | }
67 | case '|' : return '|';
68 | case '#' : return '⊥'
69 | default : return c;
70 | }
71 | }
72 |
73 | function txtchar(c,tv,cs) {
74 | switch(c) {
75 | case true : // return char based on selected truth value style
76 | switch(tv) {
77 | case 'tb': return '\u22a4';
78 | case 'tf': return 'T';
79 | case 'oz': return '1';
80 | }
81 | case false : // return char based on selected truth value style
82 | switch(tv) {
83 | case 'tb': return '\u22a5';
84 | case 'tf': return 'F';
85 | case 'oz': return '0';
86 | }
87 | case null : // return char based on selected truth value style (third truth value)
88 | switch(tv) {
89 | case 'tb': return 'N';
90 | case 'tf': return 'N';
91 | case 'oz': return 'N';
92 | }
93 | case '' : return ' ';
94 | default : return c;
95 | }
96 | }
97 |
98 | function latexchar(c,tv,cs) {
99 | switch(c) {
100 | case true : // return char based on selected truth value style
101 | switch(tv) {
102 | case 'tb': return '$\\top$';
103 | case 'tf': return 'T';
104 | case 'oz': return '1';
105 | }
106 | case false : // return char based on selected truth value style
107 | switch(tv) {
108 | case 'tb': return '$\\bot$';
109 | case 'tf': return 'F';
110 | case 'oz': return '0';
111 | }
112 | case null : // return char based on selected truth value style (third truth value)
113 | switch(tv) {
114 | case 'tb': return 'N';
115 | case 'tf': return 'N';
116 | case 'oz': return 'N';
117 | }
118 | case '~' : // return connective chars based on selected cset
119 | switch(cs) {
120 | case 'cs1': return '$\\lnot$';
121 | default : return '$\\sim$';
122 | }
123 | case '&' :
124 | switch(cs) {
125 | case 'cs1' : return '$\\land$';
126 | default : return '$\\&$';
127 | }
128 | case 'v' : return '$\\lor$';
129 | case '>' :
130 | switch(cs) {
131 | case 'cs3' : return '$\\supset$';
132 | default : return '$\\rightarrow$';
133 | }
134 | case '<>' :
135 | switch(cs) {
136 | case 'cs3' : return '$\\equiv$';
137 | default : return '$\\leftrightarrow$';
138 | }
139 | case '|' : return '$|$';
140 | case '#' : return '$\\perp$';
141 | default : return c;
142 | }
143 | }
144 |
145 | /*************************************************************************************/
146 |
147 | // main construction function
148 | function construct() {
149 | var formulas = document.getElementById('in').value.replace(/ /g,'');// remove whitespace
150 | if(formulas=='') {return alert("You have to enter a formula.");};
151 | var r = badchar(formulas);
152 | if(r>=0) {return alert("The string you entered contains the following unrecognized symbol: "+formulas[r]);};
153 |
154 | var style = document.querySelector('input[name="style"]:checked').value;
155 | var tv = document.querySelector('input[name="tvstyle"]:checked').value;
156 | var cs = document.querySelector('input[name="cset"]:checked').value;
157 |
158 | formulas = formulas.split(','); // create an array of formulas
159 | var trees = formulas.map(parse); // create an array of parse trees
160 | for(var i=0;i0) { // checks if any formulas are still malformed
168 | return alert("One of the formulas you entered is not well formed");
169 | }
170 |
171 | var table = mkTable(formulas,trees);
172 | if(style=='full' || style=='main') {
173 | var htmltable = htmlTable(table,trees,(style=='main'),tv,cs);
174 | document.getElementById('tt').innerHTML = htmltable;
175 | }
176 | else if(style=='text') {
177 | var texttable = textTable(table,tv,cs);
178 | document.getElementById('tt').innerHTML = '
';
183 | }
184 | }
185 |
186 | // (Table,[Tree],Boolean) -> String
187 | // Takes a table (as output by mkTable), the trees it's a table of, and a boolean and
188 | // returns an HTML table. If the boolean is set to true, it only prints the column
189 | // under the main connective.
190 | function htmlTable(table,trees,flag,tv,cs) {
191 | var rownum = table[0].length;
192 | var mcs = []; // indices of the main connectives
193 | for(var i=0;i'; // return the html table
202 |
203 | function mkTHrow(tbl) {
204 | var rw = '
';
234 | }
235 | }
236 |
237 | // Table -> String
238 | // Takes a table (as output by mkTable) and returns a text version of the table.
239 | function textTable(table,tv,cs) {
240 | var rownum = table[0].length;
241 | var bcind = []; // an array of arrays of ints, locations of biconditionals
242 | for(var i=0;i=0 && r!=0) {rw += ' ';} // add space for biconditional
259 | if(j==tbl[i][r].length-1 && i!=tbl.length-1) {rw += '| ';} // add segment separator
260 | }
261 | }
262 | return rw;
263 | }
264 | function bcInd(a) {
265 | var bc = [];
266 | a.map(function(e,i) {if(e=='<>') {bc.push(i);};});
267 | return bc;
268 | }
269 | }
270 |
271 |
272 | // Table -> String
273 | // Takes a table (as output by mkTable) and the trees its a table of and returns a LaTex version of the table.
274 | function latexTable(table,trees,tv,cs) {
275 | var rownum = table[0].length;
276 | var mcs = []; // indices of the main connectives
277 | for(var i=0;i=0 && dividers.indexOf(i+1)>=0) {
293 | begintable += ' | c'
294 | } else if(dividers.indexOf(i)>=0) {
295 | begintable += parloc.indexOf(i)>=0 ? ' | c@{}' : ' | c@{ }';
296 | } else if(dividers.indexOf(i+1)>=0) {
297 | begintable += parloc.indexOf(i)>=0 ? '@{}c@{ }' : '@{ }c';
298 | } else {
299 | begintable += parloc.indexOf(i)>=0 ? '@{}c@{}' : '@{ }c@{ }';
300 | }
301 | }
302 |
303 | return begintable+'}\r\n'+out+'\\end{tabular}';
304 |
305 | function mkrow(tbl,r) { // makes a table row
306 | dividers = [];
307 | colnum = 0;
308 |
309 | var rw = '';
310 | for(var i=0;i Int
329 | // Finds the index of the main connective in the tree
330 | function mcindex(t) {
331 | if(t.length == 2 || t.length==1) {
332 | return 0;
333 | } else {
334 | return countleaves(t[1])+1;
335 | }
336 | }
337 |
338 | // Tree -> Int
339 | // Takes a tree and returns the number of leaves (terminal nodes) in the tree
340 | function countleaves(t) {
341 | var out = 0;
342 | for(var i=0;i Table
351 | // Takes an array of formulas and their parse trees and returns a truth table as a
352 | // multidimensional array. For n formulas, the array contains n+1 elements. The first
353 | // element is the lhs of the table, and the succeeding elements are the table segments
354 | // for each passed formula.
355 | function mkTable(fs,ts) {
356 | var type = document.querySelector('input[name="type"]:checked').value; // get type of table, two-valued or otherwise
357 | var lhs = mklhs(fs,type);
358 | var rhs = [];
359 | for(var i=0;i LHSTable
366 | // Takes an array of strings and a table type, makes the left hand side of a table
367 | // (i.e. the rows with all the tv assignments)
368 | function mklhs(fs,type) {
369 | var atomic = [];
370 | var tvrows = [];
371 | var tvs = (type=='twoval') ? [[true],[false]] : [[true],[false],[null]]; // send two or three truth values to tvcfun?
372 | for(var i=0;i TableSegment
382 | // Takes a tree, the formula it's a tree of, a LHSTable, and table type, returns a
383 | // TableSegment for the formula
384 | function mktseg(f,t,lhs,type) {
385 | var tbrows=[];
386 | for(var i=1;i [Char]
402 | // Takes a wff and returns an array with all the atomic sentences in the wff. The
403 | // array has duplicates removed and is sorted in alphabetical order.
404 | function getatomic(s) {
405 | var out = [];
406 | for(var i=0;i Int -> [[val]]
413 | // Piece de resistance, totally illegible ;-) Takes an array of singleton arrays and an
414 | // int n and returns the n-ary Cartesian power of the set of singleton values. In
415 | // practice: takes either [[true],[false]] or [[true], [false], [null]] and the number n
416 | // of atomic formulas and returns and array of all 2- or 3-valued assignments to those
417 | // atoms in a canonical order, i.e. the left side of the truth table
418 | function tvcfun(a,n) {
419 | if(n==0) {return [[]];}
420 | var prev = tvcfun(a,(n-1));
421 | return a.reduce(foo,[]);
422 | function foo(ac,cv) {
423 | return ac.concat(prev.map((e) => cv.concat(e)));
424 | }
425 | }
426 |
427 | // ([Char], [Bool+]) -> Assignment
428 | // Takes an array of n Chars and an array of n Bools and returns a assignment that
429 | // assigns the nth Bool to the nth Char.
430 | function mkAss(s,b) {
431 | var a = new Object();
432 | for(var i=0;i Array
439 | // Takes an evaluated tree and turns it into a one dimensional array
440 | function flatten(t) {
441 | if(t.length==5) {
442 | return [].concat(t[0]).concat(flatten(t[1])).concat(t[2]).concat(flatten(t[3])).concat(t[4]);
443 | } else if(t.length==2) {
444 | return [].concat(t[0]).concat(flatten(t[1]));
445 | } else if(t.length==1) {
446 | return [].concat(t[0]);
447 | }
448 | }
449 |
450 | // (Tree, Assignment, TType) -> Tree
451 | // Takes a tree and an assignment of booleans to atomic sentences and table type, returns
452 | //an evaluated tree (i.e. with all atomic sentences and connectives replaced by booleans).
453 | function evlTree(t,a,type) {
454 | if(t.length==5) {
455 | var t1 = evlTree(t[1],a,type);
456 | var t3 = evlTree(t[3],a,type);
457 | return ['',t1,gtTv([t[2],t1,t3],type),t3,'']
458 | } else if(t.length==2) {
459 | var t1 = evlTree(t[1],a,type);
460 | return [gtTv([t[0],t1],type),t1];
461 | } else if(t.length==1) {
462 | return t[0]=='#' ? [false] : [a[t[0]]];
463 | }
464 | }
465 |
466 | // Array -> Boolean+
467 | // Takes an array, the first element of which is a connective, and the rest of which
468 | // are evaluated trees of the formulas it connects, and returns the truth value
469 | // associated with the connective
470 | function gtTv(arr,type) {
471 | switch(arr[0]) {
472 | case '~' : return fneg(tv(arr[1]));
473 | case '&' : return fcnj(tv(arr[1]),tv(arr[2]),type);
474 | case 'v' : return fdsj(tv(arr[1]),tv(arr[2]),type);
475 | case '>' : return fcnd(tv(arr[1]),tv(arr[2]),type);
476 | case '<>' : return fcnj( fcnd(tv(arr[1]),tv(arr[2]),type), fcnd(tv(arr[2]),tv(arr[1]),type), type );
477 | case '|' : return fneg(fcnj(tv(arr[1]),tv(arr[2]),type));
478 | case '!' : return fneg(fdsj(tv(arr[1]),tv(arr[2]),type));
479 | }
480 | function tv(x) {
481 | switch(x.length) {
482 | case 5 : return x[2];
483 | case 2 : return x[0];
484 | case 1 : return x[0];
485 | }
486 | }
487 | }
488 |
489 | // Boolean+ -> Boolean+
490 | // Negation: takes one truth value, no table type needed since all agree on negation
491 | function fneg(v) {
492 | return v===null ? null : !v;
493 | }
494 |
495 | // (Boolean+, Boolean+, TType) -> Boolean+
496 | // Conjunction: takes two truth values and a table type
497 | function fcnj(v1,v2,type) {
498 | if(type==='twoval' || type=='luka' || type=='skleen') {
499 | return (v1===null) ? ((v2==null) ? null : (v2 ? null : false)) : ((v2===null) ? (v1 ? null : false) : (v1 && v2));
500 | } else if(type==='wkleen') {
501 | return (v1===null || v2===null) ? null : (v1 && v2);
502 | }
503 | }
504 |
505 | // (Boolean+, Boolean+, TType) -> Boolean+
506 | // Disjunction: takes two truth values and a table type
507 | function fdsj(v1,v2,type) {
508 | if(type==='twoval' || type=='luka' || type=='skleen') {
509 | return (v1===null) ? ((v2==null) ? null : (v2 ? true : null)) : ((v2===null) ? (v1 ? true : null) : (v1 || v2));
510 | } else if(type=='wkleen') {
511 | return (v1===null || v2===null) ? null : (v1 || v2);
512 | }
513 | }
514 |
515 | // (Boolean+, Boolean+, TType) -> Boolean+
516 | // Conditional: takes two truth values and a table type
517 | function fcnd(v1,v2,type) {
518 | if(type==='twoval' || type=='luka') {
519 | return (v1===null) ? ((v2==null) ? true : (v2 ? true : null)) : ((v2===null) ? (v1 ? null : true) : (!v1 || v2));
520 | } else if(type=='skleen') {
521 | return (v1===null) ? ((v2==null) ? null : (v2 ? true : null)) : ((v2===null) ? (v1 ? null : true) : (!v1 || v2));
522 | } else if(type=='wkleen') {
523 | return (v1===null || v2===null) ? null : (!v1 || v2);
524 | }
525 | }
526 |
527 | // Remove duplicates from an array
528 | function rmDup(a) {
529 | return a.filter(function(el,pos) {return a.indexOf(el)==pos;});
530 | }
531 |
532 | // [Char] -> [Char]
533 | // Takes an array of chars and returns the array sorted from smallest to largest
534 | function sorted(a) {
535 | var b = a.map(function(x) {return x.charCodeAt(0);});
536 | b = b.sort(function(b,c) {return b-c;});
537 | return b.map(function(x) {return String.fromCharCode(x);});
538 | }
539 |
540 |
541 | // FORMULA PARSING CODE
542 | //====================
543 |
544 | /* THE GRAMMAR
545 | S ::= U S | '(' S B S ')' | A
546 | U ::= '~'
547 | B ::= '&' | 'v' | '>' | '<>' | '|' | '!'
548 | A ::= '#' | 'A' | 'B' | 'C' | 'D' | ... | 'a' | 'b' | ...
549 | */
550 |
551 | // String -> Tree
552 | // Takes a string and if it's a wff, returns a parse tree of the string, otherwise
553 | // returns an empty array.
554 | function parse(s) {
555 | if(s.length==0) {return [];}
556 | var s1 = [];
557 | var s2 = [];
558 | if(isU(s[0])) {
559 | s1 = parse(s.substring(1));
560 | return s1.length ? [s[0],s1] : [];
561 | }
562 | if(s[0] =='(' && s[s.length-1]==')') {
563 | var a = gSub(s);
564 | if(a.indexOf(undefined)>=0 || a.indexOf('')>=0) {
565 | return [];
566 | } else {
567 | s1 = parse(a[0]);
568 | s2 = parse(a[2]);
569 | if(s1.length && s2.length) {
570 | return ['(',s1,a[1],s2,')'];
571 | } else {return [];}
572 | }
573 | }
574 | else {return isA(s) ? [s] : []}
575 | }
576 |
577 | // String -> Bool
578 | // Determines if s is an atomic wff
579 | function isA(s) {
580 | if(s.length!=1) {return false;}
581 | var pr = '#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuwxyz';
582 | return pr.indexOf(s)>=0;
583 | }
584 |
585 |
586 | // String -> Bool
587 | // Determines if s begins with a unary connective
588 | function isU(s) {
589 | return s.indexOf('~')==0;
590 | }
591 |
592 | // String -> [String]
593 | // takes a string beginning with '(' and ending with ')', and determines if there is a
594 | // binary connective enclosed only by the outermost parentheses. If so, returns an array
595 | // with the string to the left and the string to the right of the binary connective;
596 | // otherwise returns an array of three undefined's.
597 | function gSub(s) {
598 | var stk = [];
599 | var l = 0;
600 | for(var i=0;i0) {
604 | stk.pop();
605 | } else if(stk.length==1 && (l = isB(s.substring(i)))>0) {
606 | return [s.substring(1,i),s.substring(i,i+l),s.substring(i+l,s.length-1)];
607 | }
608 | }
609 | return [undefined,undefined,undefined];
610 | }
611 |
612 | // String -> Int
613 | // takes a string and determines if it begins with a binary connective. If so, returns
614 | // the length of the connective, otherwise returns 0.
615 | function isB(s) {
616 | var bc = ['&','v','>','<>','|','!'];
617 | for(var i=0;i Int
626 | // Checks if the string contains any inadmissible characters
627 | function badchar(s) {
628 | var x = ',()~v&<>|!#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuwxyz';
629 | for(var i=0;i