├── .gitignore ├── docs ├── QED.html ├── main.css ├── classes.txt └── js │ ├── main.js │ ├── gui.js │ └── logic.js ├── LICENSE ├── README.md └── HISTORY.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ -------------------------------------------------------------------------------- /docs/QED.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QED - Moved 5 | 6 | 7 | 8 | 9 |

QED - Home page moved (slightly)

10 |

11 | For convenience, Version 2.4 of QED moved the home page from— 12 |

16 | This page will automatically redirect you in 20 seconds. 17 | Update your bookmarks, etc. 18 |

19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 teorth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .wrapper { 6 | display: grid; 7 | grid-auto-columns: 32%; 8 | grid-gap: 10px; 9 | } 10 | 11 | .box { 12 | padding: 10px; 13 | } 14 | 15 | .root { 16 | grid-column: 1; 17 | grid-row: 1; 18 | } 19 | .term { 20 | grid-column: 1; 21 | grid-row: 2; 22 | } 23 | .formula { 24 | grid-column: 2; 25 | grid-row: 1; 26 | } 27 | .deductions { 28 | grid-column: 3; 29 | grid-row: 1/4; 30 | } 31 | 32 | .operators { 33 | grid-column: 2; 34 | grid-row: 2; 35 | } 36 | 37 | .halfbox { 38 | float: left; 39 | width: 50%; 40 | padding: 1px; 41 | } 42 | 43 | .clearfix::after { 44 | content: ""; 45 | clear: both; 46 | display: table; 47 | } 48 | 49 | .clickable { 50 | box-shadow: 0 0 2px 1px rgba(0,0,0,0.2); 51 | border-radius: 2px; 52 | } 53 | .clickable:hover { 54 | box-shadow: 0 1px 2px 1px rgba(0,0,0,0.5); 55 | } 56 | 57 | table { 58 | font-family: arial, sans-serif; 59 | border-collapse: collapse; 60 | width: 100%; 61 | } 62 | 63 | td, th { 64 | border: 1px solid #dddddd; 65 | text-align: left; 66 | padding: 8px; 67 | } 68 | 69 | tr:nth-child(even) { 70 | background-color: #dddddd; 71 | } 72 | 73 | #dataContainer { 74 | display: none; 75 | } 76 | 77 | .hidden { 78 | display: none; 79 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QED 2 | 3 | Repository for the QED interactive text and possible extensions 4 | 5 | Try the latest version at: 6 | 7 | This project was initially created by Terence Tao and released on July 28, 8 | 2018 at http://www.math.ucla.edu/~tao/QED/QED.html (old version). On Aug 21, 9 | 2018, the project was uploaded to GitHub in order to open up the project to 10 | other coders. 11 | 12 | 13 | ## File Structure 14 | 15 | The source code is stored in the [`docs`](docs) folder (to permit 16 | [auto-deploying the code using GitHub Pages][github-publishing]). It consists 17 | of these main files: 18 | 19 | 1. [`index.html`](docs/index.html) - the web page for the text. 20 | 21 | 2. [`main.css`](docs/main.css) - CSS stylesheet. 22 | 23 | 3. JavaScript ([`docs/js`](docs/js) folder) 24 | 25 | * [`logic.js`](docs/js/logic.js) - the code for the logical elements of the 26 | text (terms, operators, sentences, contexts, etc.). The most complex 27 | portion of the code is probably the matching algorithms that look for 28 | all the possible deductions that can be made from selected sentences 29 | given the laws available. 30 | 31 | * [`gui.js`](docs/js/gui.js) - code for buttons, boxes, and other GUI 32 | elements. 33 | 34 | * [`main.js`](docs/js/main.js) - mainly loads the content (exercises, notes, 35 | solutions, etc) into the HTML document. 36 | 37 | In addition there is a text file at [`classes.txt`](docs/classes.txt) that 38 | gives a "cheat sheet" summary of the main data structures used in the 39 | javascript code. 40 | 41 | ## History 42 | 43 | The version history can be found here: [`HISTORY.md`](HISTORY.md). 44 | 45 | ## License 46 | 47 | The code is open source under the [MIT License](LICENSE). 48 | 49 | 50 | [github-publishing]: https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch 51 | -------------------------------------------------------------------------------- /docs/classes.txt: -------------------------------------------------------------------------------- 1 | // a pseudo-C++ description of the main classes appearing in the code 2 | 3 | 4 | /////////////////// logical components of the code - mostly in logic.js 5 | 6 | /// Global variables 7 | 8 | Sentence sentences[]; // sentences[name] is a sentence/term attached to a name string; easier to populate this dynamically than to create a general string to sentence/term parser. 9 | Law unlockedLaws[]; // all the unlocked laws 10 | Law allLaws[]; // list of all Laws 11 | var lawsByShortName = {}; // laws indexed by shortname 12 | 13 | 14 | 15 | // a FreeVariable is a variable such as x, y, z that are not bound by quantifiers. 16 | 17 | FreeVariable { 18 | string type; // "free variable" 19 | string subtype; // name of variable 20 | string name; // name of variable 21 | string Name; // name of variable in italics 22 | } 23 | 24 | // BoundVariable is a variable such as X, Y, Z that can be bound by quantifiers. 25 | 26 | BoundVariable { 27 | string type; // "bound variable" 28 | string subtype; // name of variable 29 | string name; // name of variable (in italics) 30 | string longName; // name of variable 31 | } 32 | 33 | // Operators are functions such as f(), g(,) or binary operations such as +, -, *, which take terms as input and return a term as output. 34 | 35 | Operator { 36 | string type; // "operator" 37 | string subtype; // name of operator 38 | string text; // name of operator in italics 39 | int arity; // number of arguments 40 | bool additionStyle; // is it an op like +? 41 | } 42 | 43 | // A Term is an expression such as g(x,Y), alpha or X+z that can be constructed from free and bound variables, primitive terms, and operators. 44 | 45 | Term { 46 | string type; // "term" 47 | string subtype; // "free variable", "bound variable", "primitive", or "operator" 48 | string shortText; // how term is displayed standalone 49 | strong longText; // how term is displayed inside more complex expressions 50 | Operator operator; // the operator used (for "operator" subtype) 51 | Term argList[]; // arguments or variables used 52 | } 53 | 54 | // note: right now, a distinction is made between a term that consists of a free or bound variable, and the free or bound variable itself. For instance, the term "x" is not the same as the free variable "x", the latter being the argList[0] of the former. On reflection, though, there is not much point to this distinction, and one could reduce the number of data structures slightly by treating free and bound variables more like primitive terms (so that a term "x" now is the same as the free variable "x" and argList is now empty). On the other hand there isn't much advantage to changing things either (a few lines of code might get a little simpler, but also a bunch of bugs might be introduced). 55 | 56 | 57 | // A Predicate is a symbol such as P(), Q(,), or a relation such as =, >, which takes terms as input and return sentences as output. The equality relation = requires special treatment. 58 | 59 | Predicate { 60 | string type; // "predicate" 61 | string subtype; // name of predicate 62 | string text; // name of predicate in italics 63 | string arity; // number of arguments 64 | bool relationStyle; // is it a relation like =? 65 | } 66 | 67 | Predicate equality; // the equality relation 68 | 69 | // A logical connective such as AND, OR, NOT, etc. takes statements as input and returns a statement as output. 70 | 71 | Connective { 72 | string name; // name of connective 73 | int arity; 74 | } 75 | 76 | Connective ANDConnective, ORConnective, NOTConnective, IMPLIESConnective, IFFConnective, TRUEConnective, FALSEConnective; 77 | 78 | // A sentence is an assertion which can be true or false in any given environment (for given choices of free variables). For instance P(x) AND (NOT Q(x+y)) would be a sentence that would be well-formed in any environment containing x and y as free variables. 79 | 80 | Sentence { 81 | string type; // "primitive", "connective", "quantifier" 82 | string subtype; // "AND", "OR", "NOT", "IMPLIES", "IFF", "TRUE", "FALSE" for connectives; "atomic" or "predicate" for primitives; "for all" or "there exists" for quantifiers 83 | string name // name of sentence when used standalone 84 | string longName; // name of sentence when combined with other sentences 85 | Sentence argList[]; // the list of arguments in this sentence (usually of length 0, 1, or 2) 86 | Predicate predicate; // predicate used ("predicate" subtype only) 87 | Connective connective; // connective used ("connective" subtype only) 88 | } 89 | 90 | // Sentences and terms have very similar structure, and some of the code treats them in a unified fashion. If one were to redo the data structures one could consider unifying the two (this would also unify the concepts of a connective, operator, and predicate). 91 | 92 | 93 | // A law is a rule of deduction that has sentence templates as input, and another sentence template as output. (For instance, Modus ponens "Given A, A IMPLIES B: deduce B" is a law.) Some laws are too complex to be fully captured by these templates due to side conditions, and need to be handled separately. 94 | 95 | Law { 96 | string shortName; // id of law 97 | string name; // HTML name of law 98 | Context givens[]; // hypotheses 99 | Context conclusion; // conclusion 100 | Context givensTemplate[]; // hypothesis template (used for matching) 101 | Context conclusionTemplate; // conclusion template (used for matching) 102 | bool unlocked; // is it available for use? 103 | string string; // name of law when invoked 104 | int index; // index in the list of laws 105 | Law clone; // in case one can add a root environment hypothesis 106 | } 107 | 108 | // The following are the list of special laws that are coded separately. Originally they were placed in the QED file; now they are defined in logic.js. 109 | 110 | Law universalIntroduction, universalIntroduction2, universalSpecification, universalSpecification2, existentialInstantiation, existentialInstantiation2, existentialIntroduction, existentialIntroduction2, indiscernability; 111 | 112 | 113 | // an Assumption is what separates an environment from a subenvironment. Examples include "Assume A", "Let x be arbitrary", and "Set x s.t. P(x)". 114 | 115 | Assumption { 116 | string type // "assuming", "letting", "setting" 117 | Sentence sentence; // the sentence being assumed (for "assuming" and "setting" types) 118 | FreeVariable variable; // the free variable used (for "letting" and "setting types") 119 | string name; // name of assumption 120 | } 121 | 122 | // A Context is a sentence inside an environment (an ordered list of assumptions), a formula in the Formulas window, or a Term in the Terms window. The main operation of the text is dragging one context onto another (or clicking on a context) to reveal potential deductions. 123 | 124 | Context { 125 | string type; // "sentence in environment", "environment", "formula", "term context" 126 | Sentence sentence; // sentence used (for sentence, sentence-in-envrionment, and formula types) 127 | Assumption environment[]; // environment used (for sentence-in-environment and environment types) 128 | Term term; // term used (For term context types) 129 | 130 | string name; // name of context 131 | } 132 | 133 | 134 | 135 | 136 | ///////////////////////////////////// GUI elements 137 | 138 | // Global variables 139 | 140 | Button exerciseButtons[]; // ordered list of activated exercise buttons 141 | Exercise exerciseList[]; // list of all exercises (including those not yet unlocked) 142 | Exercise exercisesByShortName[]; // list of exercises (including those not yet unlocked) indexed by shortname 143 | Button lastClickedButton; // this will be updated to the last deduction button one clicked; prevents double clicking from doing anything 144 | bool revealTrueFalse; // do we populate the formula window with true and false? 145 | String sectionTitle; // name of last section to be created 146 | int numIndexedLaws; // number of laws that have been properly assigned an index 147 | 148 | 149 | 150 | // An Exercise, such as Exercise 4.1, asks the reader to establish some conclusion from given hypothesis. It also may come with some supplementary text, unlock some laws or windows when beginning the exercise, and unlock some further exercises when completed. 151 | 152 | Exercise { 153 | string shortName; // short name of exercise (without "EXERCISE ") 154 | string name; // name of exercise 155 | Law law; // the law the exercise seeks to prove 156 | Law newLaws[]; // the laws unlocked when starting exercise 157 | Exercise newExercises[]; // the exercises unlocked when completing exercise 158 | string notes; // the notes for the exercise, displayed on opening 159 | bool revealFormulaWindow; // do we unlock the formula window? 160 | bool revealTermWindow; // do we unlock the term window? 161 | bool revealTrueFalse; // do we unlock TRUE and FALSE? 162 | bool revealBoundButton; // do we unlock the new bound var button? 163 | int bestLength; // the shortest known length of proof 164 | string proof; // the shortest known proof of this exercise 165 | int personalBest; // personal best length of proof 166 | bool activated; // has this exercise been unlocked? 167 | Button button; // the button created by this exercise 168 | 169 | function unlocks(law); // add law to the laws unlocked by this exercise 170 | function unlockedBy(exercise2); // add this exercise to the exercises unlocked by exercise2 171 | } 172 | 173 | 174 | // the undo button is constructed by createUndoButton(). 175 | 176 | UndoButton extends button { 177 | bool canUndo; // whether undo is available 178 | element deletionList[]; // list of elements to be deleted on undo 179 | int numlines; // what to rewind numlines back to 180 | bool hasCircularity; // what to rewind hasCircularity back to 181 | } 182 | 183 | 184 | // inside the proof box there is an ordered list containing the proof. 185 | 186 | proof extends orderedList { 187 | int numlines; // number of lines in the proof 188 | bool hasCircularity; // does the proof use something at or after the exercise? 189 | } 190 | 191 | // the exercise Box lists the exercise 192 | 193 | ExerciseBox extends box { 194 | Exercise exercise; // the current exercise 195 | ExerciseButton exerciseButton; // the current exercise button 196 | } 197 | 198 | // a droppable text field for receiving arguments for operators and predicates 199 | 200 | ArgDroppable extends span { 201 | OperatorItem li; the list element the field belongs to 202 | int i; the index of the field (1 or 2) 203 | } 204 | 205 | // a line item containing an operator or predicate in the Operators and Predicates window 206 | 207 | OperatorItem extends li { 208 | Operator operator; // the operator in question (can also be Predicate) 209 | Term args[]; // the argument list (default ""). 210 | ArgDroppable fields[]; // the list of text fields 211 | } 212 | 213 | // the box for an environment 214 | environmentBox extends box { 215 | Assumption assumption; // the assumption for this box 216 | string type; // "environment" 217 | } 218 | 219 | // the box for a sentence 220 | 221 | sentenceBox extends box { 222 | Sentence sentence; // the ambient sentence; 223 | string type; // "sentenceBox" 224 | } 225 | 226 | formulaBox extends box { 227 | Sentence sentence; // the ambient sentence; 228 | string type; // "formulaBox" 229 | } 230 | 231 | termBox extends box { 232 | Term term; // the ambient term; 233 | string type; // "termBox" 234 | } 235 | 236 | 237 | 238 | 239 | exerciseButton extends button { 240 | Exercise exercise; // the exercise associated to button 241 | bool solved; // has the exercise been solved yet? 242 | bool enabled; // is the button eabled yet? 243 | String sectionTitle; // what is the title of the section the button is in? 244 | } 245 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # QED - Release History 2 | 3 | * **Version 2.10.7** (released Jan 24, 2025). New record for 22.5(bcd); correction to indiscernability of identicals. 4 | 5 | * **Version 2.10.7** (released Oct 15, 2019). Fixed bug in which the running list of assumptions was not reset when restarting or changing an exercise. 6 | 7 | * **Version 2.10.6** (released Oct 13, 2019). Added the ability to hide/unhide rules that occurred at or after the current exercise with the 'c' key. 8 | 9 | * **Version 2.10.5** (released May 6, 2019). New record for 14.1. 10 | 11 | * **Version 2.10.4** (released Apr 12, 2019). Added to the Exercise 3.1(a) text to explain how to select three or more hypotheses at once. 12 | 13 | * **Version 2.10.3** (released Dec 24, 2018). New record for 22.3(b). 14 | 15 | * **Version 2.10.2** (released Oct 10, 2018). "use strict" implemented, some accidentally global variables now local. New records for 22.8, 22.4(cd), 22.5(abcde). 16 | * **Version 2.10.1** (released Oct 8, 2018). Matching fixed for 22.4(bd). Fixed a technical matching bug involving matching bound variables. New record for 22.5(d). 17 | 18 | * **Version 2.10** (released Oct 8, 2018). New record for 22.5(bc). New helper exercise (12.6(d)). 19 | 20 | * **Version 2.9.3** (released Oct 6, 2018). New record for 24.8. 21 | 22 | * **Version 2.9.2** (released Oct 6, 2018). New record for 24.8. Added links to similar "logic games". 23 | 24 | * **Version 2.9.1** (released Oct 3, 2018). New record for 24.8. 25 | 26 | * **Version 2.9** (released Oct 2, 2018). New exercise (24.8), suggested by Martin Epstein. 27 | 28 | * **Version 2.8.6** (released Sep 16, 2018). New record for 22.5(b). 29 | 30 | * **Version 2.8.5** (released Sep 2, 2018). New records for 22.5(ade). 31 | 32 | * **Version 2.8.4** (released Sep 1, 2018). Matching coded for Ex 18.3(a) (Barbara syllogism, singular form). 33 | 34 | * **Version 2.8.3** (released Sep 1, 2018). Matching coded for Ex 18.1 (renaming universal bound variable). Code added to ensure legacy solved exercises still load all relevant laws. Added an existential counterpart to Ex 18.1, Ex 22.6(c). 35 | 36 | * **Version 2.8.2** (released Aug 31, 2018). New records for 22.3(b), 22.5(ab), 22.8. 37 | 38 | * **Version 2.8.1** (released Aug 30, 2018). New records for 22.4(cd). "Unsolve exercise" button added to reset the solved status of an exercise (can be useful for legacy exercises which have been altered by version changes). 39 | 40 | * **Version 2.8** (released Aug 30, 2018). Proxy matching implemented for several more exercises (including all of the syllogism ones). This feature may not be completely bug-free. 41 | 42 | * **Version 2.7.6** (released Aug 30, 2018). Proxy matching templates for laws implemented, allowing in particular for matching for laws 22.2 and 22.6(a). More to follow shortly. 43 | 44 | * **Version 2.7.5** (released Aug 30, 2018). Exercises now loaded from an array, section by section. 45 | 46 | * **Version 2.7.4** (released Aug 30, 2018). Made explicit the exercises for which matching is not currently implemented. 47 | 48 | * **Version 2.7.3** (released Aug 30, 2018). New records for Exercise 9.5(c), 12.4(a), 22.4(abe), 24.7. Exercise 1.1 changed to something more useful and less cluttering; text for first few exercises adjusted accordingly. 49 | 50 | * **Version 2.7.2** (released Aug 29, 2018). Change the Exercise constructor 51 | to require a Law object. 52 | 53 | * **Version 2.7.1** (released Aug 29, 2018). Remove support for passing a 54 | non-empty string lawName to the Exercise constructor. 55 | 56 | * **Version 2.7** (released Aug 29, 2018). Several new helper exercises added to Sections 8-12. (Note that due to exercise renumbering, some exercises may need to be reproven.) 57 | 58 | * **Version 2.6.3** (released Aug 29, 2018). Ability to delete sentences and environments added. 59 | 60 | * **Version 2.6.2** (released Aug 29, 2018). Duplicate sentences or terms are no longer produced in windows by deduction rules. 61 | 62 | * **Version 2.6.1** (released Aug 29, 2018). "Previous exercise" and "Next exercise" buttons added, together with "<" and ">" hotkeys. 63 | 64 | * **Version 2.6** (released Aug 29, 2018). Split Predicates and Operators window into two. Fixed a bug with naming of law clones. Reversed order of notifications window, which is now renamed to "Event log" window; availability notifications arising from loading from localStorage is now disabled. Fixed missing ExistentialInstantiation law unlocks in Ex 19.1. 65 | 66 | * **Version 2.5.13** (released Aug 29, 2018). Refactored laws to be partially drawn from HTML rather than javascript. Alternate form of FALSE law added (triggering on the FALSE formula rather than NOT FALSE) 67 | 68 | * **Version 2.5.12** (released Aug 29, 2018). Fix the unlocking of 69 | Reflexivity and Indiscernability 70 | ([PR #16](https://github.com/teorth/QED/pull/16)). 71 | 72 | * **Version 2.5.11** (released Aug 29, 2018). Add `data-reveals` attribute 73 | to HTML exercise elements. 74 | 75 | * **Version 2.5.10** (released Aug 29, 2018). Make exercise 11.1 unlock 76 | "NOT" again ([issue #14](https://github.com/teorth/QED/issues/14)). 77 | 78 | * **Version 2.5.9** (released Aug 28, 2018). Spaces in HTML element id's changed to hyphens. Ids and display names decoupled in reveal() function. 79 | 80 | * **Version 2.5.8** (released Aug 28, 2018). Exercise text window initially hidden to prevent "flicker" effect on startup. Exercise 1.1 completion message recoded to activate whenever an exercise is solved for the first time. 81 | 82 | * **Version 2.5.7** (released Aug 28, 2018). Law unlock information now stored in HTML rather than in javascript. Some redundant variable names now removed. 83 | 84 | * **Version 2.5.6** (released Aug 28, 2018). Spaces and other special characters removed from law short names. (This may cause some disruptions to any laws unlocked between 2.5.3 and 2.5.6.) 85 | 86 | * **Version 2.5.5** (released Aug 28, 2018). Edit state popup window replaced by a custom window in order to evade text length restrictions in Chrome popup windows. 87 | 88 | * **Version 2.5.4** (released Aug 27, 2018). Exercise names are now stored in HTML rather than as javascript strings. 89 | 90 | * **Version 2.5.3** (released Aug 27, 2018). Laws now have a short name which is used in local storage (and in future, for lookup in HTML). 91 | 92 | * **Version 2.5.2** (released Aug 27, 2018). Typos in text for Exercises 24.1(a) and 24.6 corrected (thanks to Antoine Deleforge for pointing them out). 93 | 94 | * **Version 2.5.1** (released Aug 27, 2018). Computed best known proof lengths automatically from stored proof, rather than manual entry. 95 | 96 | * **Version 2.5** (released Aug 27, 2018). Added an "EDIT STATE" button to permit save/restore of states, and to transfer states from legacy versions of the text. 97 | 98 | * **Version 2.4.8** (released Aug 27, 2018). Change `"unlockedby"` to 99 | `"data-unlocked-by"`. 100 | 101 | * **Version 2.4.7** (released Aug 26, 2018). Add initial support for an 102 | `"unlockedby"` attribute of the exercise HTML elements. 103 | 104 | * **Version 2.4.6** (released Aug 26, 2018). Migrated notes and proof to HTML format. 105 | 106 | * **Version 2.4.5** (released Aug 26, 2018). Fixed typo in Exercise 24.1 notes. Removed alpha release statement in Exercise 14.1. 107 | 108 | * **Version 2.4.4** (released Aug 26, 2018). Moved remaining JS files 109 | `logic.js` and `gui.js` to `js` folder. 110 | 111 | * **Version 2.4.3** (released Aug 26, 2018). Error in Exercise 24.3 corrected. 112 | 113 | * **Version 2.4.2** (released Aug 26, 2018). Noscript added to HTML file. 114 | 115 | * **Version 2.4.1** (released Aug 26, 2018). Move the remaining JS inside 116 | `index.html` into a separate file `js/main.js`. 117 | 118 | * **Version 2.4** (released Aug 25, 2018). Change the main page from 119 | `QED.html` to `index.html` so the URL can be the simpler 120 | [https://teorth.github.io/QED/](https://teorth.github.io/QED/) 121 | ([issue #4](https://github.com/teorth/QED/issues/4)). 122 | 123 | * **Version 2.3.3** (released Aug 25, 2018). Move CSS to `main.css`. 124 | 125 | * **Version 2.3.2** (released Aug 25, 2018). New record for Exercise 24.7. 126 | 127 | * **Version 2.3.1** (released Aug 25, 2018). Moved version history to 128 | `HISTORY.md` in the GitHub repo. 129 | 130 | * **Version 2.3** (released Aug 24, 2018). Two more challenging exercises 131 | added (24.6, inverse is an involution; and 24.7, irrational to irrational 132 | can be rational). Subtitle of Exercise 24.1(a) fixed. 133 | 134 | * **Version 2.2.2** (released Aug 24, 2018). Several records shortened using 135 | double pull. 136 | 137 | * **Version 2.2.1** (released Aug 24, 2018). New record for Exercise 24.4. 138 | 139 | * **Version 2.2** (released Aug 24, 2018). New exercise (24.5, product of 140 | invertibles; also another helper push exercise). Bug with locating next 141 | available free and bound variables (inadvertently introduced in 2.1) now 142 | fixed. 143 | 144 | * **Version 2.1.5** (released Aug 24, 2018). New exercise (24.4, 145 | cancellation law; also two helper push exercises). 146 | 147 | * **Version 2.1.4** (released Aug 24, 2018). New record for Exercise 22.8. 148 | 149 | * **Version 2.1.3** (released Aug 23, 2018). New records for Exercises 22.8, 150 | 23.2. Attribution restored for Exercise 22.4(e). Typo in root environment 151 | fixed (thanks to Yahya Abdal-Aziz for reporting this). 152 | 153 | * **Version 2.1.2** (released Aug 23, 2018). Further bugfix with circularity 154 | detection. New record for 9.2(a). 155 | 156 | * **Version 2.1.1** (released Aug 23, 2018). Some quotes added to text; new 157 | exercise (IFF is reflexive, Exercise 10.2(c)) 158 | 159 | * **Version 2.1** (released Aug 23, 2018). New exercises (Russell's paradox, 160 | Exercise 23.2, no largest natural number, Exercise 23.3, and a Lewis 161 | Carroll logic puzzle, Exercise 22.8) added. Fixed a bug introduced in 2.0.3 162 | with alternate versions of deduction laws being interpreted as circular. 163 | 164 | * **Version 2.0.3** (released Aug 22, 2018). Minified CSS. Some code moved 165 | from QED.html file to logic.js file (and some moved from logic.js to 166 | gui.js) to make the latter stand-alone. 167 | 168 | * **Version 2.0.2** (released Aug 21, 2018). Code moved to Github, now under 169 | an MIT license. 170 | 171 | * **Version 2.0.1** (released Aug 20, 2018). Meta-key now has same 172 | functionality as CTRL-key (for Mac users). (Thanks to Jacob H. for the 173 | suggestion.) 174 | 175 | * **Version 2.0** (released Aug 18, 2018). Cleaned up version of Version 176 | 1.17, hopefully stable release. 177 | 178 | * **Version 1.17** (released Aug 16, 2018). Alpha version of Section 24, 179 | introducing the equality relation. 180 | 181 | * **Version 1.16.1** (released Aug 15, 2018). New records for 22.4(ae), 182 | using some additional formulas that were not previously available until the 183 | predicates and operators window was unlocked. (These formulas have since 184 | been added to the problem.) 185 | 186 | * **Version 1.16** (released Aug 13, 2018). Alpha version of Section 23, 187 | introducing the predicates and operators window. 188 | 189 | * **Version 1.15.5** (released Aug 13, 2018). New record for 22.6(b). 190 | Spelling corrections. 191 | 192 | * **Version 1.15.4** (released Aug 13, 2018). Text for Exercise 22.4(ce) and 193 | 22.5(a) fixed (thanks to Anders Kaseorg for pointing out the issue). Three 194 | new exercises added to Section 22. 195 | 196 | * **Version 1.15.3** (released Aug 12, 2018). New records for Exercises 197 | 22.4(acd), 22.5(c). Text for Exercise 22.4(c) fixed. 198 | 199 | * **Version 1.15.2** (released Aug 12, 2018). New records for Exercises 200 | 18.4, 22.2. Further bug in universal specification legality checking 201 | patched (thanks to Andre Maute for reporting the error), as well as a 202 | technical bug in the search-and-replace code. UI for pull (arbitrary 203 | variables) changed. 204 | 205 | * **Version 1.15.1** (released Aug 12, 2018). Bug with variable matching 206 | fixed (thanks to dP dt and Andre Maute for reporting the error). Also 207 | patched bugs in universal specification and universal introduction (thanks 208 | to dP dt for reporting the first error). 209 | 210 | * **Version 1.15** (released Aug 11, 2018). Alpha version of Section 22, 211 | introducing the existential introduction law (the first law in which one 212 | can have multiple deductions from a single choice of inputs for reasons 213 | other than permutation of the inputs). Several "invisible" changes to data 214 | structures. Some helper exercises in earlier sections added. 215 | 216 | * **Version 1.14.3** (released Aug 9, 2018). Sections and exercises layout 217 | revamped (thanks to dP dt for the suggestion). Exercises now come with the 218 | section title as well. 219 | 220 | * **Version 1.14.2** (released Aug 9, 2018). Instructions for Exercise 21.1 221 | corrected (thanks to Andrew Lei for pointing out the error). Link to blog 222 | post added at bottom of page (thanks to dP dt for the suggestion). 223 | 224 | * **Version 1.14.1** (released Aug 9, 2018). More accurate discussion of 225 | boolean duality in Exercise 12.5(d). New record for Exercise 18.6. Enforced 226 | a further requirement in the law of universal specification, namely that 227 | the term one specifies also cannot use free variables that are not 228 | recognised by the current environment (thanks to Pranjal Vachaspati for 229 | discovering this issue). 230 | 231 | * **Version 1.14** (released Aug 9, 2018). Alpha versions of Sections 20, 21 232 | added, introducing a "pull" law and also an existence law to rule out empty 233 | domains of discourse. Section 13 rearranged and expanded, with a new helper 234 | exercise (13.2(b)) that shortens some subsequent proofs. Enforced an 235 | important requirement in the law of universal specification, namely that 236 | the term one specifies to cannot involve bound variables. 237 | 238 | * **Version 1.13.1** (released Aug 8, 2018). Text of Exercise 19.1 fixed. 239 | Double push exercise added in Exercise 15.2. Deductions resulting in 240 | ill-formed statements will now be displayed but grayed out, rather than 241 | hidden completely (thanks to Keith Winstein for this suggestion). Layout of 242 | main windows rearranged. 243 | 244 | * **Version 1.13** (released Aug 7, 2018). Alpha version of Section 19 245 | added, introducing the existential quantifier and the ability to 246 | instantiate an existential statement using a "setting" environment. Section 247 | 10 reorganised with a new exercise intended to shorten the proofs of 248 | subsequent exercises proving IFF statements (in particular, several 249 | exercises in Section 10 onwards (particularly those whose conclusion 250 | involves IFF) now have shorter proofs). New hotkeys: 'n' for first 251 | available unsolved exercise, 'N' for last available unsolved exercise. New 252 | record for 9.5(a). Bug with new free and bound variables in Exercise 253 | 18.2(a) fixed, as well as incorrect text for Exercise 18.4 (thanks to Keith 254 | Winstein for reporting these bugs). 255 | 256 | * **Version 1.12** (released Aug 7, 2018). Alpha version of Section 18 257 | added, introducing the ability to specialise a universal statement to a 258 | term. Primitive terms also introduced. 259 | 260 | * **Version 1.11** (released Aug 7, 2018). Alpha version of Section 17 261 | added, introducing the ability to introduce a universal quantifier over a 262 | bound variables (which is now equipped with a button to generate such 263 | variables). Some bugs with finding available deductions fixed. Alternate 264 | push law functionality added in which one can push to an environment not 265 | yet created by dragging to a formula rather than to the environment. (As a 266 | consequence, Exercises 7.1 and 7.2 now have shorter proofs.) Bad hyperlink 267 | (to idempotence) fixed. 268 | 269 | * **Version 1.10.1** (released Aug 6, 2018). An exploit involving enabling a 270 | deduction, undoing the previous deduction, then selecting a (now 271 | unjustified) deduction has been patched (thanks to Keith Winstein for 272 | finding the exploit). Some rewording for the Exercise 1.1 text (following 273 | suggestions by arch1). 274 | 275 | * **Version 1.10** (released Aug 5, 2018). Alpha version of Section 16 276 | added, introducing the ability to introduce free variables from a new Term 277 | window (which is also equipped with a button to generate new variables). 278 | Code separated into logic.js, which contains the GUI-independent components 279 | of the code, and gui.js, which contains the GUI-dependent components (for 280 | future modularity). 281 | 282 | * **Version 1.9** (released Aug 4, 2018). Environments now clickable. 283 | Hotkeys "u" for immediate undo and "r" for restart. Alpha version of 284 | Sections 14, 15 added, introducing free variable environments and atomic 285 | sentences involving predicates and free variables, and a new PUSH law. Some 286 | bad hyperlinks (reported by Mauricio de Oliveira) repaired. Fixed a bug 287 | (reported by Matthew Steffen) regarding whether cloned laws qualified for 288 | non-circular proofs. Some laws taking a target environment as an input now 289 | have that environment removed in view of the clone functionality. 290 | 291 | * **Version 1.8.1** (released Aug 4, 2018). Error in PUSH law (and other 292 | laws involving a non-root environment) fixed. (Thanks to Andre Maute for 293 | reporting the bug.) 294 | 295 | * **Version 1.8** (released Aug 3, 2018). Restart exercise button added. 296 | Further "under the hood" changes. Laws with root environment default now 297 | come with a "clone" with one additional hypothesis that allows one to 298 | select the root environment. 299 | 300 | * **Version 1.7.1** (released Aug 2, 2018). Can now use ctrl-click to select 301 | items, in particular allowing for more than two items to be selected. The 302 | numbers 1-9 are now keyboard shortcuts for the first 9 deductions in the 303 | Available deductions window. 304 | 305 | * **Version 1.7** (released Aug 2, 2018). Dragging X to Y now also checks 306 | for laws available from dragging Y to X. Exercise 1.1 changed (so legacy 307 | players may artificially have beaten the shortest length now for this 308 | exercise). Various "under the hood" code changes in preparation for 309 | extension to first order logic. Various sections of game now collapsed by 310 | default. 311 | 312 | * **Version 1.6.2** (released Aug 1, 2018). New records for Exercises 313 | 9.5(b), 10.1(b), 13.2(a), 13.3(a), 13.3(d). Duplicate unlocking of laws 314 | prevented. Table of boolean duality added to Exercise 12.5(d). Typo 315 | corrections. 316 | 317 | * **Version 1.6.1** (released July 31, 2018). New records for Exercises 318 | 9.5(a), 13.3(a), 13.3(d). 319 | 320 | * **Version 1.6** (released July 31, 2018). New 0-ary operators TRUE and 321 | FALSE; new exercises (and a new section) added. 322 | 323 | * **Version 1.5.3** (released July 31, 2018). Rules appearing in text at or 324 | after an exercise can still be used, but have an asterisk attached to them 325 | and no longer trigger records. 326 | 327 | * **Version 1.5.2** (released July 30, 2018). New record for Exercise 10.4. 328 | 329 | * **Version 1.5.1** (released July 30, 2018). New record for Exercise 330 | 9.3(d). Improved backwards capability (game states from prior versions 331 | should now be able to re-unlock exercises and laws). 332 | 333 | * **Version 1.5** (released July 30, 2018). Completing all exercises allows 334 | one to reveal the shortest known proofs. Some inaccuracies in the shortest 335 | known proofs corrected. A bug regarding the interaction between the UNDO 336 | button and the number of lines of the proof has been fixed. 337 | 338 | * **Version 1.4** (released July 30, 2018). Exercises organised by section. 339 | Solved exercises are now green or blue depending on whether the shortest 340 | length was achieved. HTML links now open in a new tab so as not to disrupt 341 | game state. Immediate UNDO button added. 342 | 343 | * **Version 1.3.2** (released July 30, 2018). New records for Exercises 344 | 2.2(b), 12.5(c). Clarifications and typo fixes to text. 345 | 346 | * **Version 1.3.1** (released July 30, 2018). Implemented the styling 347 | suggestions of redblobgames. 348 | 349 | * **Version 1.3** (released July 30, 2018). Newer record for Exercise 350 | 12.6(a). Bad hyperlink (pointed out by Andrew Lei) fixed. Successful proofs 351 | can now be expanded and collapsed in the notifications window. 352 | 353 | * **Version 1.2.2** (released July 29, 2018). New record for Exercise 354 | 12.6(a). A typo causing exercises to not load beyond 12.2(b) has been 355 | repaired. (Thanks to William Chargin for pointing out the issue and 356 | providing a fix.) 357 | 358 | * **Version 1.2.1** (released July 29, 2018). New records for Exercises 359 | 7.2, 9.2(a), 12.2(b), 12.2(c), 12.4(b), 12.6(c). Some incorrect proof 360 | lengths fixed. 361 | 362 | * **Version 1.2** (released July 29, 2018). Achievements and notifications 363 | windows now side-by side. Achievements now list from newest to oldest 364 | rather than vice versa. "No available deductions" response added. Clicking 365 | on deductions twice no longer creates duplicates (thanks to ahartel for 366 | this suggestion). Some clarifications added to text. Best known proof 367 | lengths added. Exercises 5.1 and 12.1(c) corrected; other minor 368 | corrections. Some new exercises added. 369 | 370 | * **Version 1.1** (released July 29, 2018). Tests if local storage is 371 | supported. Exercise 7.2 repaired (and all subsequent exercises now 372 | accessible). All exercise buttons now visible (not just unlocked ones). 373 | Spelling corrections. 374 | 375 | * **Version 1.0** (released July 28, 2018). Beta release. 376 | -------------------------------------------------------------------------------- /docs/js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | createExerciseButtonBox(); 4 | createExerciseBox(); 5 | createRootEnvironment(); 6 | createFormulaWindow(); 7 | createDeductionsBox(); 8 | createProofBox(); 9 | createTermWindow(); 10 | createAchievementsBox(); 11 | createNotificationsBox(); 12 | createOperatorsWindow(); 13 | createUndoButton(); 14 | createRestartButton(); 15 | createUnsolveButton(); 16 | createPrevExerciseButtons(); 17 | createNextExerciseButtons(); 18 | createResetButton(); 19 | createEditStateButton(); 20 | document.body.setAttribute("onkeydown", "keydown(event)"); 21 | 22 | // predicates, terms, sentences, functions, operators that are used in exercises and laws. 23 | 24 | var x = new FreeVariable("x"); 25 | var y = new FreeVariable("y"); 26 | var z = new FreeVariable("z"); 27 | 28 | var P = new Predicate("P", 1, false); 29 | var Q = new Predicate("Q", 1, false); 30 | var R = new Predicate("R", 1, false); 31 | var S = new Predicate("S", 1, false); 32 | var P2 = new Predicate("P", 2, false); 33 | var Q2 = new Predicate("Q", 2, false); 34 | var R2 = new Predicate("R", 2, false); 35 | var P3 = new Predicate("P", 3, false); 36 | var Q3 = new Predicate("Q", 3, false); 37 | var R3 = new Predicate("R", 3, false); 38 | var gt = new Predicate(">", 2, true); 39 | var lt = new Predicate("<", 2, true); 40 | var gte = new Predicate("≥", 2, true); 41 | var elementOf = new Predicate("∈", 2, true); 42 | 43 | var f = new Operator("f", 1, false); 44 | var sqrt = new Operator("√", 1, false); 45 | var plus = new Operator("+", 2, true); 46 | var times = new Operator("*", 2, true); 47 | var exp = new Operator("^", 2, true); 48 | 49 | function multiply(x,y) { return operatorTerm(times, [x,y]); } 50 | function power(x,y) { return operatorTerm(exp, [x,y]); } 51 | 52 | var X = new BoundVariable("X"); 53 | var Y = new BoundVariable("Y"); 54 | var Z = new BoundVariable("Z"); 55 | 56 | var one = primitiveTerm("1"); 57 | var two = primitiveTerm("2"); 58 | var alpha = primitiveTerm("α"); 59 | var beta = primitiveTerm("β"); 60 | var gamma = primitiveTerm("γ"); 61 | 62 | 63 | var fa = operatorTerm(f, [alpha]); 64 | var fb = operatorTerm(f, [beta]); 65 | var fX = operatorTerm(f, [X]); 66 | var ffX = operatorTerm(f, [fX]); 67 | var sqrt2 = operatorTerm(sqrt, [two]); 68 | var sqrtX = operatorTerm(sqrt, [X]); 69 | var X1 = operatorTerm(plus, [X,one]); 70 | 71 | var Px = predicateSentence(P,[x]); 72 | var Qx = predicateSentence(Q,[x]); 73 | var Rx = predicateSentence(R,[x]); 74 | var Sx = predicateSentence(S,[x]); 75 | var Qy = predicateSentence(Q,[y]); 76 | var PX = predicateSentence(P,[X]); 77 | var PY = predicateSentence(P,[Y]); 78 | var QX = predicateSentence(Q,[X]); 79 | var QY = predicateSentence(Q,[Y]); 80 | var RX = predicateSentence(R,[X]); 81 | var RY = predicateSentence(R,[Y]); 82 | var SX = predicateSentence(S,[X]); 83 | var Pa = predicateSentence(P,[alpha]); 84 | var Pb = predicateSentence(P,[beta]); 85 | var Qa = predicateSentence(Q,[alpha]); 86 | var Qxy = predicateSentence(Q2,[x,y]); 87 | var QXY = predicateSentence(Q2,[X,Y]); 88 | var Qaa = predicateSentence(Q2,[alpha,alpha]); 89 | var QXa = predicateSentence(Q2,[X,alpha]); 90 | var QaX = predicateSentence(Q2,[alpha,X]); 91 | var gtXY = predicateSentence(gt, [X,Y]); 92 | var PXplusY = predicateSentence(P, [operatorTerm(plus, [X,Y])]); 93 | var XinX = predicateSentence(elementOf, [X,X]); 94 | var YinX = predicateSentence(elementOf, [Y,X]); 95 | var YinY = predicateSentence(elementOf, [Y,Y]); 96 | var XgteY = predicateSentence(gte, [X,Y]); 97 | var XgteX1 = predicateSentence(gte, [X,X1]); 98 | 99 | 100 | 101 | // A mapping from law shortName to the pair [givens, conclusion]. 102 | var lawsData = { 103 | "LawConjunction1": [[A, B], AND(A,B)], 104 | "LawConjunction2": [[AND(A,B)], A], 105 | "LawConjunction3": [[AND(A,B)], B], 106 | "LawDisjunction1": [[formulaContext(B), A], OR(A,B)], 107 | "LawDisjunction2": [[formulaContext(B), A], OR(B,A)], 108 | "LawAssumption": [[formulaContext(A)], assuming(A,A)], 109 | "And": [[formulaContext(A), formulaContext(B)], formulaContext(AND(A,B))], 110 | "Or": [[formulaContext(A), formulaContext(B)], formulaContext(OR(A,B))], 111 | "Implies": [[formulaContext(A), formulaContext(B)], formulaContext(IMPLIES(A,B))], 112 | "LawImplication": [[assuming(B,A), rootEnvironmentContext()], IMPLIES(A,B)], 113 | "Push": [[A, environmentContext([B])], assuming(A,B)], 114 | "PushAlt": [[A, formulaContext(B)], assuming(A,B)], 115 | "ModusPonens": [[A, IMPLIES(A,B)], B], 116 | "CaseAnalysis": [[assuming(C,A), assuming(C,B)], assuming(C,OR(A,B))], 117 | "Iff": [[formulaContext(A), formulaContext(B)], formulaContext(IFF(A,B))], 118 | "BiconditionalIntroduction": [[IMPLIES(A,B), IMPLIES(B,A)], IFF(A,B)], 119 | "BiconditionalElimination1": [[IFF(A,B)], IMPLIES(A,B)], 120 | "BiconditionalElimination2": [[IFF(A,B)], IMPLIES(B,A)], 121 | "Not": [[formulaContext(A)], formulaContext(NOT(A))], 122 | "caseElimination1": [[OR(A,B), NOT(A)], B], 123 | "caseElimination2": [[OR(A,B), NOT(B)], A], 124 | "ExcludedMiddle": [[formulaContext(A)], OR(A,NOT(A))], 125 | "True": [[formulaContext(TRUE())], TRUE()], 126 | "False": [[formulaContext(NOT(FALSE()))], NOT(FALSE())], 127 | "False2": [[formulaContext(FALSE())], NOT(FALSE())], 128 | "PushVar": [[A, x], assuming(A, x)], 129 | "free": [[toTerm(x)], x], 130 | "forAll": [[formulaContext(A), toTerm(X)], formulaContext(forAll(A,X))], 131 | "thereExists": [[formulaContext(A), toTerm(X)], formulaContext(thereExists(A,X))], 132 | "PushSet": [[A, settingAssumption(B,x)], assuming(A, settingAssumption(B,x))], 133 | "Pull": [[assuming(A, settingAssumption(B,x))], A], 134 | "Pull2": [[assuming(A, settingAssumption(B,x)), rootEnvironmentContext()], A], 135 | "Existence": [[TRUE(), X], thereExists(TRUE(),X)], 136 | "Existence2": [[formulaContext(TRUE()), X], thereExists(TRUE(),X)], 137 | "Reflexivity": [[alpha], equals(alpha,alpha)], 138 | "UniversalIntroduction": [[assuming(Px,x),toTerm(X)], forAll(PX,X)], 139 | "UniversalIntroduction2": [[assuming(Px,x), rootEnvironmentContext()], forAll(PX,X)], 140 | "UniversalRenamingBoundVar": [[forAll(PX,X),toTerm(Y)], forAll(PY,Y)], 141 | "BarbaraSingular": [[forAll(IMPLIES(PX,QX),X), Pa, alpha], Qa], 142 | "ExistentialRenamingBoundVar":[[thereExists(PX,X),toTerm(Y)], thereExists(PY,Y)], 143 | "UniversalSpecification": [[forAll(PX,X), alpha], Pa], 144 | "UniversalSpecification2": [[forAll(PX,X), toTerm(x)], assuming(Px,x)], 145 | "ExistentialInstantiation": [[thereExists(PX,X), toTerm(x)], assuming(Px, settingAssumption(Px,x))], 146 | "ExistentialInstantiation2": [[thereExists(PX,X)], assuming(Px, settingAssumption(Px,x))], 147 | "ExistentialIntroduction": [[Pa, alpha], thereExists(PX,X)], 148 | "ExistentialIntroduction2": [[Pa, alpha, X], thereExists(PX,X)], 149 | "Indiscernability": [[Pa, equals(alpha,beta)], Pb], 150 | }; 151 | 152 | var lawElements = getElement("lawContainer").children; 153 | 154 | for (var i = 0; i < lawElements.length; i++) { 155 | var div = lawElements[i]; 156 | var id = div.id; 157 | // An example id is "law-LawConjunction1". 158 | var shortName = id.split("-", 2)[1]; 159 | var lawName = div.innerHTML; 160 | 161 | var pair = lawsData[shortName]; 162 | if (!pair) { 163 | throw new Error("law shortName not found: " + shortName); 164 | } 165 | 166 | var givens = pair[0]; 167 | var conclusion = pair[1]; 168 | new Law(shortName, lawName, givens, conclusion); 169 | } 170 | 171 | // A mapping from exercise shortName to the pair [givens, conclusion] (or to [shortname-of-existing-law]). 172 | var exerciseData = { 173 | "1.1": [[A, B, C], AND(AND(A,B),C)], 174 | "1.2": [[A], AND(A,A)], 175 | "2.1": [[AND(A,B)], AND(B,A)], 176 | "2.2(a)": [[AND(AND(A,B),C)], AND(A,AND(B,C))], 177 | "2.2(b)": [[AND(A,AND(B,C))], AND(AND(A,B),C)], 178 | "3.1(a)": [[A, formulaContext(B), formulaContext(C)], OR(C,OR(A,B))], 179 | "3.1(b)": [[A], OR(A,A)], 180 | "4.1": [[formulaContext(A)], assuming(AND(A,A),A)], 181 | "5.1": [[formulaContext(A), formulaContext(B), formulaContext(C)], assuming(OR(AND(A,B),C), OR(AND(A,B),C))], 182 | "5.2": [[formulaContext(A), formulaContext(B)], assuming(assuming(A,A),B)], 183 | "6.1(a)": [[formulaContext(A)], IMPLIES(A,A)], 184 | "6.1(b)": [[formulaContext(A), formulaContext(B)], IMPLIES(AND(A,OR(A,B)),A)], 185 | "6.2": [[assuming(assuming(C,B),A), rootEnvironmentContext()], IMPLIES(A,IMPLIES(B,C))], 186 | "7.1": [[formulaContext(A), formulaContext(B)], IMPLIES(A, IMPLIES(B,A))], 187 | "7.2": [[A, environmentContext([B,C])], assuming(assuming(A,C),B)], 188 | "8.1(a)": [[A, assuming(B,A)], B], 189 | "8.1(b)": [[IMPLIES(A,B)], assuming(B,A)], 190 | "8.2": [[IMPLIES(A,B), IMPLIES(B,C)], IMPLIES(A,C)], 191 | "8.3": [[formulaContext(A), formulaContext(B)], IMPLIES(AND(A,B),AND(B,A))], 192 | "8.4(a)": [[IMPLIES(A,IMPLIES(B,C))], IMPLIES(AND(A,B),C)], 193 | "8.4(b)": [[IMPLIES(AND(A,B),C)], IMPLIES(A,IMPLIES(B,C))], 194 | "8.4(c)": [[IMPLIES(A,IMPLIES(B,C))], IMPLIES(B,IMPLIES(A,C))], 195 | "8.5(a)": [[IMPLIES(A,B)], IMPLIES(A, AND(A,B))], 196 | "8.5(b)": [[IMPLIES(A,B)], IMPLIES(A, AND(B,A))], 197 | "8.6(a)": [[AND(A,B), IMPLIES(A,C)], AND(C,B)], 198 | "8.6(b)": [[AND(A,B), IMPLIES(B,C)], AND(A,C)], 199 | "9.1(a)": [[OR(A,B)], OR(B,A)], 200 | "9.1(b)": [[OR(A,A)], A], 201 | "9.2(a)": [[OR(OR(A,B),C)], OR(A,OR(B,C))], 202 | "9.2(b)": [[OR(A,OR(B,C))], OR(OR(A,B),C)], 203 | "9.3(a)": [[OR(A, AND(B,C))], AND(OR(A,B), OR(A,C))], 204 | "9.3(b)": [[AND(A, OR(B,C))], OR(AND(A,B), AND(A,C))], 205 | "9.3(c)": [[OR(AND(A,B), AND(A,C))], AND(A, OR(B,C))], 206 | "9.3(d)": [[AND(OR(A,B), OR(A,C))], OR(A, AND(B,C))], 207 | "9.4(a)": [[OR(A,B), IMPLIES(A,C)], OR(C,B)], 208 | "9.4(b)": [[OR(A,B), IMPLIES(B,C)], OR(A,C)], 209 | "9.4(c)": [[OR(A,B), IMPLIES(A,C), IMPLIES(B,D)], OR(C,D)], 210 | "9.5(a)": [[formulaContext(C), IMPLIES(A,B)], IMPLIES(AND(A,C), AND(B,C))], 211 | "9.5(b)": [[formulaContext(C), IMPLIES(A,B)], IMPLIES(OR(A,C), OR(B,C))], 212 | "9.5(c)": [[formulaContext(C), IMPLIES(A,B)], IMPLIES(AND(C,A), AND(C,B))], 213 | "9.5(d)": [[formulaContext(C), IMPLIES(A,B)], IMPLIES(OR(C,A), OR(C,B))], 214 | "10.1(a)": [[A, IFF(A,B)], B], 215 | "10.1(b)": [[AND(A,B), IFF(A,C)], AND(C,B)], 216 | "10.1(c)": [[AND(A,B), IFF(B,C)], AND(A,C)], 217 | "10.1(d)": [[OR(A,B), IFF(A,C)], OR(C,B)], 218 | "10.1(e)": [[OR(A,B), IFF(B,C)], OR(A,C)], 219 | "10.1(f)": [[IMPLIES(A,B), IFF(A,C)], IMPLIES(C,B)], 220 | "10.1(g)": [[IMPLIES(A,B), IFF(B,C)], IMPLIES(A,C)], 221 | "10.2(a)": [[IFF(A,B)], IFF(B,A)], 222 | "10.2(b)": [[IFF(A,B), IFF(B,C)], IFF(A,C)], 223 | "10.2(c)": [[formulaContext(A)], IFF(A,A)], 224 | "10.3": [[assuming(A,B), assuming(B,A)], IFF(A,B)], 225 | "10.4": [[formulaContext(A)], IFF(A, OR(A,A))], 226 | "10.5": [[formulaContext(A), formulaContext(B), formulaContext(C)], IFF(AND(AND(A,B),C),AND(A,AND(B,C)))], 227 | "11.1": [[AND(A,NOT(A)), formulaContext(B)], B], 228 | "11.2": [[AND(NOT(A),A), formulaContext(B)], B], 229 | "12.1(a)": [[assuming(AND(B,NOT(B)),A)], NOT(A)], 230 | "12.1(b)": [[assuming(B,A), assuming(NOT(B),A)], NOT(A)], 231 | "12.1(c)": [[assuming(B,A), assuming(B,NOT(A))], B], 232 | "12.1(d)": [[formulaContext(A)], OR(NOT(A),A)], 233 | "12.2(a)": [[NOT(NOT(A))], A], 234 | "12.2(b)": [[A], NOT(NOT(A))], 235 | "12.2(c)": [[formulaContext(A)], IFF(A,NOT(NOT(A)))], 236 | "12.3(a)": [[assuming(AND(B,NOT(B)),NOT(A))], A], 237 | "12.3(b)": [[assuming(AND(NOT(B),B),NOT(A))], A], 238 | "12.4(a)": [[IMPLIES(A,B)], OR( NOT(A), B)], 239 | "12.4(b)": [[OR( NOT(A), B)], IMPLIES(A,B)], 240 | "12.4(c)": [[formulaContext(A), formulaContext(B)], IFF(IMPLIES(A,B), OR( NOT(A), B))], 241 | "12.5(a)": [[NOT(OR(A,B))], AND(NOT(A), NOT(B))], 242 | "12.5(b)": [[OR(NOT(A), NOT(B))], NOT(AND(A,B))], 243 | "12.5(c)": [[NOT(AND(A,B))], OR(NOT(A), NOT(B))], 244 | "12.5(d)": [[AND(NOT(A), NOT(B))], NOT(OR(A,B))], 245 | "12.6(a)": [[IMPLIES(A,B)], IMPLIES(NOT(B),NOT(A))], 246 | "12.6(b)": [[IMPLIES(A,B), NOT(B)], NOT(A)], 247 | "12.6(c)": [[NOT(A), IFF(A,B)], NOT(B)], 248 | "12.6(d)": [[IMPLIES(A,NOT(B)), B], NOT(A)], 249 | "12.7": [[IMPLIES(IMPLIES(A,B),A)], A], 250 | "12.8": [[OR(A,B), OR(NOT(A),C)], OR(C,B)], 251 | "13.1(a)": [[formulaContext(A)], IFF(A,AND(TRUE(),A))], 252 | "13.1(b)": [[formulaContext(A)], IFF(A,OR(FALSE(),A))], 253 | "13.2(a)": [[formulaContext(A)], NOT(AND(FALSE(),A))], 254 | "13.2(b)": [[formulaContext(A)], OR(TRUE(),A)], 255 | "13.3(a)": [[formulaContext(A)], IMPLIES(FALSE(),A)], 256 | "13.3(b)": [[formulaContext(A)], IMPLIES(A,TRUE())], 257 | "13.3(c)": [[formulaContext(A)], IFF(A, IMPLIES(TRUE(),A))], 258 | "13.3(d)": [[formulaContext(A)], IFF(NOT(A), IMPLIES(A,FALSE()))], 259 | "14.1": [[assuming(Px,x)], assuming(IMPLIES(Qx,AND(Px,Qx)),x)], 260 | "15.1": [[assuming(Px,x), assuming(assuming(Qxy,y),x)], assuming(assuming(AND(Px,Qxy),y),x)], 261 | "15.2(a)": [[A, environmentContext([x,y])], assuming(assuming(A,y),x)], 262 | "15.2(b)": [[A, environmentContext([B,x])], assuming(assuming(A,x),B)], 263 | "15.2(c)": [[A, environmentContext([x,B])], assuming(assuming(A,B),x)], 264 | "16.1": [[formulaContext(Qxy)], assuming(assuming(OR(Qxy, NOT(Qxy)),y),x)], 265 | "17.1": [[formulaContext(Px)], forAll(IMPLIES(PX,PX),X)], 266 | "17.2": [[formulaContext(QXY)], IMPLIES(forAll(forAll(QXY,Y),X),forAll(forAll(QXY,Y),X))], 267 | "18.1": ["UniversalRenamingBoundVar"], 268 | "18.2(a)": [[A, toTerm(X)], forAll(A,X)], 269 | "18.2(b)": ["UniversalSpecification2"], 270 | "18.3(a)": ["BarbaraSingular"], 271 | "18.3(b)": [[forAll(IMPLIES(PX,QX),X), forAll(IMPLIES(RX,PX),X)], forAll(IMPLIES(RX,QX),X), 272 | [forAll(IMPLIES(A,B),X), forAll(IMPLIES(C,A),X)], forAll(IMPLIES(C,B),X)], 273 | "18.4": [[forAll(forAll(QXY,Y),X)], forAll(forAll(QXY,X),Y), 274 | [forAll(forAll(A,Y),X)], forAll(forAll(A,X),Y)], 275 | "18.5": [[formulaContext(PX), formulaContext(QX), toTerm(X)], IFF( forAll( AND(PX,QX), X ), AND(forAll(PX,X), forAll(QX,X))), 276 | [formulaContext(A), formulaContext(B), toTerm(X)], IFF( forAll( AND(A,B), X), AND(forAll(A,X), forAll(B,X)))], 277 | "18.6": [[formulaContext(A), formulaContext(PX), toTerm(X)], IFF( IMPLIES(A, forAll( PX, X )), forAll(IMPLIES(A,PX),X)), 278 | [formulaContext(A), formulaContext(B), toTerm(X)], IFF( IMPLIES(A, forAll( B, X )), forAll(IMPLIES(A,B),X))], 279 | "19.1": [[thereExists(PX,X), forAll(QX,X)], assuming(AND(Px,Qx), settingAssumption(Px,x))], 280 | "19.2(a)": [[A, environmentContext([B, settingAssumption(C,x)])], assuming(assuming(A,settingAssumption(C,x)),B)], 281 | "19.2(b)": [[A, environmentContext([settingAssumption(C,x), B])], assuming(assuming(A,B),settingAssumption(C,x))], 282 | "19.2(c)": [[A, environmentContext([settingAssumption(C,x), y])], assuming(assuming(A,y),settingAssumption(C,x))], 283 | "19.2(d)": [[A, environmentContext([y,settingAssumption(C,x)])], assuming(assuming(A,settingAssumption(C,x)),y)], 284 | "19.2(e)": [[A, environmentContext([settingAssumption(B,y),settingAssumption(C,x)])], assuming(assuming(A,settingAssumption(C,x)),settingAssumption(B,y))], 285 | "20.1": [[thereExists(A,X)], A], 286 | "20.2": [[assuming(assuming(A, settingAssumption(B,x)),settingAssumption(C,y)),rootEnvironmentContext()], A], 287 | "21.1": [[assuming(A, x), rootEnvironmentContext()], A], 288 | "22.1": [[Qaa], thereExists(AND(QaX,QXa),X)], 289 | "22.2": [[forAll(PX,X)], thereExists(PX,X), 290 | [forAll(A,X)], thereExists(A,X)], 291 | "22.3(a)": [[formulaContext(PX), X, formulaContext(Px)], IFF( NOT(thereExists(PX, X)), forAll(NOT(PX),X)), 292 | [formulaContext(A), X], IFF( NOT( thereExists(A,X)), forAll(NOT(A), X))], 293 | "22.3(b)": [[formulaContext(PX), X, formulaContext(Px)], IFF( NOT(forAll(PX, X)), thereExists(NOT(PX),X)), 294 | [formulaContext(A), X], IFF( NOT( forAll(A,X)), thereExists(NOT(A), X))], 295 | "22.4(a)": [[NOT(thereExists(AND(PX,QX),X)), forAll(IMPLIES(RX,PX),X), formulaContext(Px), formulaContext(Qx), formulaContext(Rx)], NOT(thereExists(AND(RX,QX),X)), 296 | [NOT(thereExists(AND(A,B), X)), forAll(IMPLIES(C,A),X)], NOT(thereExists(AND(C,B),X))], 297 | "22.4(b)": [[forAll(IMPLIES(PX,QX),X), thereExists(AND(RX,PX),X)], thereExists(AND(RX,QX),X), 298 | [forAll(IMPLIES(A,B),X), thereExists(AND(C,A),X)], thereExists(AND(C,B),X)], 299 | "22.4(c)": [[NOT(thereExists(AND(PX,QX),X)), thereExists(AND(RX,PX),X), formulaContext(Px), formulaContext(Qx), formulaContext(Rx)], thereExists(AND(RX,NOT(QX)),X), 300 | [NOT(thereExists(AND(A,B),X)), thereExists(AND(C,A),X)], thereExists(AND(C, NOT(B)),X)], 301 | "22.4(d)": [[forAll(IMPLIES(PX,QX), X), thereExists(AND(RX,NOT(QX)),X)], thereExists(AND(RX,NOT(PX)),X), 302 | [forAll(IMPLIES(A,B),X), thereExists(AND(C, NOT(B)), X)], thereExists(AND(C, NOT(A)),X)], 303 | "22.4(e)": [[thereExists(AND(PX, NOT(QX)), X), forAll(IMPLIES(PX,RX), X), formulaContext(Px), formulaContext(Qx), formulaContext(Rx)], thereExists(AND(RX,NOT(QX)),X), 304 | [thereExists(AND(A, NOT(B)), X), forAll(IMPLIES(A,C),X)], thereExists(AND(C,NOT(B)),X)], 305 | "22.5(a)": [[forAll(IMPLIES(PX,QX), X), forAll(IMPLIES(QX,RX),X), thereExists(PX, X)], thereExists(AND(PX,RX),X), 306 | [forAll(IMPLIES(A,B), X), forAll(IMPLIES(B,C), X), thereExists(A, X)], thereExists(AND(A,C),X)], 307 | "22.5(b)": [[NOT(thereExists(AND(PX,QX),X)), forAll(IMPLIES(RX,PX),X), thereExists(RX, X), formulaContext(Px), formulaContext(Qx), formulaContext(Rx)], thereExists(AND(RX,NOT(QX)),X), 308 | [NOT(thereExists(AND(A,B), X)), forAll(IMPLIES(C,A), X), thereExists(C, X)], thereExists(AND(C, NOT(B)), X)], 309 | "22.5(c)": [[forAll(IMPLIES(PX,QX),X), NOT(thereExists(AND(RX,QX),X)), thereExists(RX,X), formulaContext(Px), formulaContext(Qx), formulaContext(Rx)], thereExists(AND(RX,NOT(PX)),X), 310 | [forAll(IMPLIES(A,B),X), NOT(thereExists(AND(C,B),X)), thereExists(C,X)], thereExists(AND(C, NOT(A)), X)], 311 | "22.5(d)": [[NOT(thereExists(AND(PX,QX),X)), forAll(IMPLIES(PX,RX),X), thereExists(PX,X), formulaContext(Px), formulaContext(Qx), formulaContext(Rx)], thereExists(AND(RX, NOT(QX)),X), 312 | [NOT(thereExists(AND(A,B), X)), forAll(IMPLIES(A,C),X), thereExists(A,X)], thereExists(AND(C, NOT(B)), X)], 313 | "22.5(e)": [[forAll(IMPLIES(PX,QX),X), forAll(IMPLIES(PX,RX),X), thereExists(PX,X)], thereExists(AND(RX, QX),X), 314 | [forAll(IMPLIES(A,B),X), forAll(IMPLIES(A,C),X), thereExists(A,X)], thereExists(AND(C, B), X)], 315 | "22.6(a)": [[thereExists(thereExists(QXY,X),Y)], thereExists(thereExists(QXY,Y),X), 316 | [thereExists(thereExists(A,X),Y)], thereExists(thereExists(A,Y),X)], 317 | "22.6(b)": [[thereExists(forAll(QXY,X),Y)], forAll(thereExists(QXY,Y),X), 318 | [thereExists(forAll(A,X),Y)], forAll(thereExists(A,Y),X)], 319 | "22.6(c)": ["ExistentialRenamingBoundVar"], 320 | "22.7": [[formulaContext(Px), formulaContext(Qy)], IFF( AND(thereExists(PX,X), thereExists(QY,Y)), thereExists(thereExists(AND(PX,QY),Y),X))], 321 | "22.8": [[forAll(IMPLIES(PX,NOT(QX)),X), forAll(IMPLIES(NOT(QX),RX),X), NOT(thereExists(AND(RX,SX),X)), formulaContext(Px), formulaContext(Qx), formulaContext(Rx), formulaContext(Sx)], forAll(IMPLIES(PX, NOT(SX)),X), 322 | [forAll(IMPLIES(A,NOT(B)),X), forAll(IMPLIES(NOT(B),C),X), NOT(thereExists(AND(C,D),X))], forAll(IMPLIES(A, NOT(D)),X)], 323 | "23.1(a)": [[], forAll(forAll(IMPLIES(QXY,QXY),Y),X)], 324 | "23.1(b)": [[], forAll(forAll(IMPLIES(gtXY,gtXY),Y),X)], 325 | "23.1(c)": [[], forAll(forAll(IMPLIES(PXplusY,PXplusY),Y),X)], 326 | "23.2": [[thereExists(forAll(IFF(YinX,NOT(YinY)),Y),X)], NOT(TRUE())], 327 | "23.3": [[forAll(NOT(XgteX1),X)], NOT(thereExists(forAll(XgteY,Y),X))], 328 | "24.1(a)": [[equals(alpha,beta)], equals(beta,alpha)], 329 | "24.1(b)": [[equals(alpha,beta), equals(beta,gamma)], equals(alpha,gamma)], 330 | "24.2": [[equals(alpha,beta)], equals(fa,fb)], 331 | "24.3": [[forAll(equals(multiply(alpha,X),X),X), forAll(equals(multiply(X,beta),X),X)], equals(alpha,beta)], 332 | "24.4": [[forAll(equals(multiply(one,X),X),X), forAll(forAll(forAll(equals(multiply(X,multiply(Y,Z)),multiply(multiply(X,Y),Z)), Z), Y), X), forAll(thereExists(equals(multiply(Y,X),one),Y),X), equals(multiply(alpha,beta),multiply(alpha,gamma))], equals(beta,gamma)], 333 | "24.5": [[forAll(equals(multiply(one,X),X),X), forAll(forAll(forAll(equals(multiply(X,multiply(Y,Z)),multiply(multiply(X,Y),Z)), Z), Y), X), thereExists(equals(multiply(X,alpha),one),X), thereExists(equals(multiply(X,beta),one),X)], thereExists(equals(multiply(X,multiply(alpha,beta)),one),X)], 334 | "24.6": [[forAll(equals(multiply(one,X),X),X), forAll(equals(multiply(X,one),X),X), forAll(forAll(forAll(equals(multiply(X,multiply(Y,Z)),multiply(multiply(X,Y),Z)), Z), Y), X), forAll(equals(multiply(fX,X),one),X)], forAll(equals(ffX,X),X)], 335 | "24.7": [[forAll(forAll(forAll(equals(power(power(X,Y),Z), power(X,multiply(Y,Z))),Z),Y),X), forAll(equals(multiply(sqrtX, sqrtX),X),X), forAll(equals(power(sqrtX,two),X),X), predicateSentence(R,[two]), NOT(predicateSentence(R,[sqrt2]))], thereExists(thereExists(AND(AND(NOT(RX),NOT(RY)), predicateSentence(R,[power(X,Y)])), Y), X)], 336 | "24.8": [[forAll(equals(multiply(one,X),X),X), forAll(forAll(forAll(equals(multiply(X,multiply(Y,Z)),multiply(multiply(X,Y),Z)), Z), Y), X), forAll(thereExists(equals(multiply(Y,X),one),Y),X)], equals(multiply(alpha,one),alpha)] 337 | }; 338 | 339 | // the newSection commands automatically create the exercises whose name begins with that section. 340 | 341 | newSection("1", "Conjunction introduction"); 342 | newSection("2", "Conjunction elimination"); 343 | newSection("3", "Disjunction introduction"); 344 | newSection("4", "Assumption"); 345 | newSection("5", "Logical connectives"); 346 | newSection("6", "Deduction theorem"); 347 | newSection("7", "Push"); 348 | newSection("8", "Modus ponens"); 349 | newSection("9", "Case analysis"); 350 | newSection("10", "The biconditional"); 351 | newSection("11", "Disjunctive elimination"); 352 | newSection("12", "Law of excluded middle"); 353 | newSection("13", "True and false"); 354 | newSection("14", "Free variables"); 355 | newSection("15", "Push (for free variables)"); 356 | newSection("16", "Free variable introduction"); 357 | newSection("17", "Universal quantification"); 358 | newSection("18", "Universal specification"); 359 | newSection("19", "Existential quantification"); 360 | newSection("20", "Pull"); 361 | newSection("21", "Existence"); 362 | newSection("22", "Existential introduction"); 363 | newSection("23", "Predicates and operators"); 364 | newSection("24", "Equality"); 365 | 366 | // load notes, proofs, best length, and unlockedBy for exercises from HTML 367 | 368 | exerciseList.forEach( function( exercise ) { 369 | var div = getElement("EXERCISE-"+exercise.shortName); 370 | if (div == null) return; 371 | 372 | var unlockedBy; 373 | if (unlockedBy = div.getAttribute("data-unlocked-by")) { 374 | var unlocker = exercisesByShortName[unlockedBy]; 375 | exercise.unlockedBy(unlocker); 376 | } else { 377 | // Otherwise, this is the first exercise. 378 | activateExerciseButton(exercise); 379 | } 380 | 381 | var unlocks; 382 | if (unlocks = div.getAttribute("data-unlocks")) { 383 | var split = unlocks.split(" "); 384 | split.forEach( function( str ) { 385 | exercise.unlocks(lawsByShortName[str]); 386 | } ); 387 | } 388 | 389 | var revealTarget; 390 | if (revealTarget = div.getAttribute("data-reveals")) { 391 | // For example, sets exercise.revealBoundButton to true. 392 | exercise["reveal" + revealTarget] = true; 393 | } 394 | 395 | if (div.getElementsByClassName("name").length > 0) { 396 | var str = div.getElementsByClassName("name")[0].innerHTML; 397 | if (str != "") 398 | { 399 | exercise.law.name = str; 400 | // need to assign name to the clone of law as well 401 | if (exercise.law.clone != "") exercise.law.clone.name = exercise.law.name; 402 | } 403 | } 404 | 405 | if (div.getElementsByClassName("notes").length > 0) 406 | exercise.notes = div.getElementsByClassName("notes")[0].innerHTML; 407 | 408 | if (div.getElementsByClassName("proof").length > 0) { 409 | var proof = div.getElementsByClassName("proof")[0]; 410 | exercise.proof= proof.innerHTML; 411 | exercise.bestLength = proof.getElementsByTagName("LI").length; 412 | } 413 | 414 | 415 | colorExerciseButton(exercise.button, false); 416 | 417 | }); 418 | 419 | 420 | 421 | // this command has to be executed after all laws and exercises defined and all GUI elements are in place 422 | checkForUnlocks(); 423 | -------------------------------------------------------------------------------- /docs/js/gui.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /// Global variables 4 | 5 | var exerciseButtons = []; // list of all activated exercise buttons 6 | var exerciseList = []; 7 | var exercisesByShortName = {}; 8 | var lastClickedButton = ""; // this will be updated to the last deduction button one clicked; prevents double clicking from doing anything 9 | var revealTrueFalse = false; // do we populate the formula window with true and false? 10 | var sectionTitle = ""; // name of last section to be created 11 | var maxLawIndex = 0; // the largest index currently assigned to a law 12 | var hideCircularLaws = false; // do we suppress laws that are circular? 13 | 14 | /////// CREATING AND UPDATING HTML ELEMENTS ///////////// 15 | 16 | function getElement(id) { 17 | return document.getElementById(id); 18 | } 19 | 20 | // a standard box 21 | 22 | function newBox() { 23 | var box = document.createElement("DIV"); 24 | box.className = "clickable"; 25 | box.style.float = "left"; 26 | box.style.margin = "5px"; 27 | box.style.padding = "0px 10px"; 28 | box.style.backgroundColor = "white"; 29 | box.style.cursor = "pointer"; 30 | return(box); 31 | } 32 | 33 | // a standard heading 34 | 35 | function newHeading(text) { 36 | var header = document.createElement("H4"); 37 | header.innerHTML = text; 38 | header.style.margin = "0px"; 39 | return header; 40 | } 41 | 42 | // a standard button 43 | 44 | function newButton(text) { 45 | var button = document.createElement("BUTTON"); 46 | button.innerHTML = text; 47 | return button; 48 | } 49 | 50 | function createResetButton() { 51 | var button = getElement("reset-button"); 52 | button.onclick = function() { 53 | if (confirm("Are you sure? This will erase all your progress!")) { 54 | if (localStorage) 55 | localStorage.clear(); 56 | location.reload(); 57 | } 58 | }; 59 | return button; 60 | } 61 | 62 | // custom prompt window 63 | 64 | function myprompt(message, defaultContent, callback) { 65 | let prompt = document.createElement("div"); 66 | prompt.classList.add("popup"); 67 | let label = document.createElement("label"); 68 | let text = document.createTextNode(message); 69 | label.appendChild(text); 70 | let input = document.createElement("input"); 71 | input.value = defaultContent; 72 | label.appendChild(input); 73 | prompt.appendChild(label); 74 | let cancel = document.createElement("button"); 75 | cancel.textContent = "Cancel"; 76 | prompt.appendChild(cancel); 77 | let ok = document.createElement("button"); 78 | ok.textContent = "Ok"; 79 | prompt.appendChild(ok); 80 | document.body.appendChild(prompt); 81 | 82 | function close() { 83 | document.body.removeChild(prompt); 84 | } 85 | 86 | function apply() { 87 | callback(input.value); 88 | } 89 | 90 | cancel.addEventListener("click", close); 91 | ok.addEventListener("click", close); 92 | ok.addEventListener("click", apply); 93 | } 94 | 95 | function createEditStateButton() { 96 | var button = getElement("edit-state-button"); 97 | button.onclick = function() { 98 | if (!localStorage) { 99 | alert("Local storage is not supported in this browser."); 100 | return; 101 | } 102 | 103 | myprompt("Enter new state here: ", JSON.stringify(localStorage), input => { 104 | if (input == null) return; 105 | localStorage.clear(); 106 | let newState = JSON.parse(input); 107 | for (let i in newState) { 108 | window.localStorage.setItem(i, newState[i]); 109 | } 110 | location.reload(); 111 | }); 112 | 113 | }; 114 | return button; 115 | } 116 | 117 | 118 | function createRestartButton() { 119 | var button = getElement("restart-button"); 120 | button.onclick = function() { 121 | var exButton = getElement("exercise").exerciseButton; 122 | if (exButton == undefined) 123 | notify("There is currently no exercise to reset."); 124 | else 125 | exButton.onclick(); 126 | }; 127 | return button; 128 | } 129 | 130 | function createUnsolveButton() { 131 | var button = getElement("unsolve-button"); 132 | button.onclick = function() { 133 | var exButton = getElement("exercise").exerciseButton; 134 | if (exButton == undefined) 135 | { 136 | notify("There is currently no exercise to reset."); 137 | return; 138 | } 139 | if (!exButton.solved) 140 | { 141 | notify("This exercise has not yet been solved."); 142 | return; 143 | } 144 | exButton.solved = false; 145 | 146 | var exercise = exButton.exercise; 147 | exercise.personalBest = 1000; 148 | 149 | if (localStorage) { 150 | localStorage.setItem(exercise.name, "unlocked"); 151 | localStorage.removeItem("lines " + exercise.name); 152 | localStorage.removeItem("proof " + exercise.name); 153 | } 154 | 155 | exButton.onclick(); 156 | }; 157 | return button; 158 | } 159 | 160 | function createUndoButton() { 161 | var button = getElement("undo-button"); 162 | button.canUndo = false; // whether undo is available 163 | button.deletionList = []; // list of elements to be deleted on undo 164 | button.numlines = 0; // what to rewind numlines back to 165 | 166 | button.onclick = function() { 167 | if (!button.canUndo) { 168 | notify("Sorry, UNDO is only available immediately after making a deduction that does not solve the exercise."); 169 | return; 170 | } 171 | button.canUndo = false; 172 | getElement("proof").numlines = button.numlines; 173 | getElement("proof").hasCircularity = button.hasCircularity; 174 | getElement("undo-button").deletionList.forEach( function(item) { item.remove(); }); 175 | notify("The deduction that was just made has now been undone."); 176 | lastClickedButton = ""; // no longer need duplication prevention 177 | }; 178 | 179 | return button; 180 | } 181 | 182 | function createPrevExerciseButtons() { 183 | var button = getElement("prev-exercise"); 184 | 185 | button.onclick = function() { 186 | var exercise = getElement("exercise").exercise; 187 | var num, i; 188 | 189 | for (i=0; i < exerciseList.length; i++) 190 | if (exerciseList[i] == exercise) { 191 | num = i; break; 192 | } 193 | 194 | if (num==0) return; 195 | var newExercise = exerciseList[num-1]; 196 | setExercise(newExercise.button); 197 | }; 198 | 199 | return button; 200 | } 201 | 202 | function createNextExerciseButtons() { 203 | var button = getElement("next-exercise"); 204 | 205 | button.onclick = function() { 206 | var exercise = getElement("exercise").exercise; 207 | var num, i; 208 | 209 | for (i=0; i < exerciseList.length; i++) 210 | if (exerciseList[i] == exercise) { 211 | num = i; break; 212 | } 213 | 214 | if (num==exerciseList.length-1) return; 215 | var newExercise = exerciseList[num+1]; 216 | if (!newExercise.activated) return; 217 | setExercise(newExercise.button); 218 | }; 219 | 220 | return button; 221 | } 222 | 223 | 224 | // create a button that collapses upon clicking. If log is enabled, it logs the innerHTML on expansion 225 | 226 | function newCollapseButton(subnode, log) 227 | { 228 | var button = newButton("+"); 229 | subnode.style.display = 'none'; 230 | button.subnode = subnode; 231 | button.onclick = clickCollapse; 232 | button.log = log; 233 | 234 | return button; 235 | } 236 | 237 | function clickCollapse() { 238 | if (this.innerHTML == '+') { 239 | this.innerHTML = '-'; 240 | this.subnode.style.display = 'block'; 241 | if (this.log) 242 | debug(this.subnode.innerHTML); 243 | } 244 | else { 245 | this.innerHTML = '+'; 246 | this.subnode.style.display = 'none'; 247 | } 248 | } 249 | 250 | function createAchievementsBox() { 251 | var box = getElement("achievement-box"); 252 | 253 | var heading = newHeading("Achievements"); 254 | box.appendChild(heading); 255 | 256 | var list = document.createElement("UL"); 257 | list.id = "achievements"; 258 | 259 | var button = newCollapseButton(list, false); 260 | heading.appendChild(button); 261 | box.appendChild(list); 262 | 263 | return box; 264 | 265 | } 266 | 267 | // record an achievement 268 | 269 | function achieve(achievement) { 270 | var log = getElement("achievements"); 271 | var node = document.createElement("LI"); 272 | var span = document.createElement("SPAN"); 273 | span.innerHTML = achievement; 274 | node.appendChild(span); 275 | log.insertBefore(node, log.firstChild); 276 | } 277 | 278 | // notifications, error messages, debugging tool 279 | 280 | function createNotificationsBox() { 281 | var box = getElement("notification-box"); 282 | var heading = newHeading("Event log"); 283 | box.appendChild(heading); 284 | 285 | var list = document.createElement("UL"); 286 | list.id = "notifications"; 287 | 288 | var button = newCollapseButton(list, false); 289 | heading.appendChild(button); 290 | box.appendChild(list); 291 | return box; 292 | } 293 | 294 | 295 | function notify(msg) { 296 | var log = getElement("notifications"); 297 | var node = document.createElement("LI"); 298 | var span = document.createElement("SPAN"); 299 | span.innerHTML = msg; 300 | node.appendChild(span); 301 | log.insertBefore(node, log.firstChild); 302 | 303 | return node; 304 | } 305 | 306 | var debugOn = true; 307 | 308 | function debug(msg) { 309 | if (debugOn) 310 | console.log(msg); 311 | } 312 | 313 | function error(msg) { 314 | notify(msg); 315 | } 316 | 317 | // create the proof box 318 | 319 | function createProofBox() { 320 | var box = getElement("proof-box"); 321 | box.appendChild(newHeading("Proof")); 322 | var list = document.createElement("OL"); 323 | list.id = "proof"; 324 | list.numlines = 0; 325 | box.appendChild(list); 326 | return box; 327 | } 328 | 329 | 330 | // add a line to the proof 331 | 332 | function appendToProof(line) { 333 | var proof = getElement("proof"); 334 | var node = document.createElement("LI"); 335 | var span = document.createElement("SPAN"); 336 | span.innerHTML = line; 337 | node.appendChild(span); 338 | proof.appendChild(node); 339 | 340 | // add node to the list of items removed by immediate undo 341 | getElement("undo-button").deletionList.push(node); 342 | proof.numlines++; 343 | } 344 | 345 | 346 | 347 | // create exercise window 348 | 349 | function createExerciseBox() { 350 | var text = getElement("exercise"); 351 | if (localStorage) 352 | { 353 | if (localStorage.length != 0) 354 | text.innerHTML = ""; 355 | } 356 | 357 | text.exercise = ""; // no exercise set yet 358 | text.exerciseButton = ""; 359 | 360 | // a hack to remove the initial hidden nature of this text field 361 | text.className = "clearfix"; 362 | 363 | return text; 364 | } 365 | 366 | // add a given hypothesis to main environment 367 | function given(context) { 368 | addContext(context); 369 | 370 | if (context.type == "sentence in environment") 371 | appendToProof(context.name + ". [given]"); 372 | else if (context.type == "environment") { 373 | var needsMention = false; // need to mention context if it contains a "letting" or "setting" 374 | var i; 375 | 376 | for (i=0; i < context.environment.length; i++) 377 | if (context.environment[i].type == 'letting' || context.environment[i].type == 'setting') 378 | needsMention = true; 379 | 380 | if (needsMention) 381 | appendToProof("Form environment " + context.name + ". [given]"); 382 | } 383 | } 384 | 385 | // clear out an element 386 | 387 | function clearElement(id) { 388 | getElement(id).innerHTML = ''; 389 | } 390 | 391 | 392 | // reveal named element 393 | function reveal(name, longName) { 394 | if (getElement(name).style.display == 'none') { 395 | achieve("UNLOCKED " + longName + "."); 396 | getElement(name).style.display = 'block'; 397 | if (localStorage) 398 | localStorage.setItem(name, "unlocked"); 399 | } 400 | } 401 | 402 | function makeArgDroppable(arg, li, i) { 403 | arg.li = li; 404 | arg.i = i; 405 | li.fields[i] = arg; 406 | arg.setAttribute('ondragover', "allowDrop(event)"); 407 | arg.setAttribute('ondrop', "argDrop(event)"); 408 | } 409 | 410 | function argDrop(ev) { 411 | ev.preventDefault(); 412 | 413 | // arg is the object that is being dragged. 414 | var arg = getElement(ev.dataTransfer.getData("text")); 415 | 416 | if (arg == null) return; // this can happen for instance if one drags a selection 417 | if (arg.type == undefined) return; // this can happen for instance if one drags a selection 418 | if (arg.type != "termBox") return; 419 | 420 | var term = arg.term; 421 | var targ = ev.target; 422 | var li = targ.li; 423 | var op = li.operator; 424 | var i = targ.i; 425 | 426 | targ.innerHTML = term.name; 427 | li.args[i] = term; 428 | 429 | // if all arguments have been selected, let's go make a term or formula! 430 | var hasAllArgs = true; 431 | var j; 432 | for (j=0; j < op.arity; j++) { 433 | if (li.args[j] == "") hasAllArgs = false; 434 | } 435 | 436 | if (!hasAllArgs) return; 437 | 438 | if (op.type == "operator") { 439 | addContext( termContext( operatorTerm( op, li.args ) ) ); 440 | } else { 441 | addContext( formulaContext( predicateSentence( op, li.args ) ) ); 442 | } 443 | 444 | // reset the fields to mdashes 445 | for (j=0; j < op.arity; j++) { 446 | li.args[j] = ""; 447 | li.fields[j].innerHTML = "—"; 448 | } 449 | } 450 | 451 | 452 | function addPredicate(name) 453 | { 454 | var obj = sentences[name]; 455 | 456 | if (obj.arity == 0) return; // don't bother puting 0-ary predicates or operators on the list (actually these are redundant anyway since atomic predicates and terms are functionally equivalent) 457 | 458 | var li = document.createElement("LI"); 459 | 460 | if (obj.type == "operator") getElement("operators-list").appendChild(li); 461 | if (obj.type == "predicate") getElement("predicates-list").appendChild(li); 462 | 463 | li.operator = obj; 464 | li.args = []; 465 | li.fields = []; 466 | var i; 467 | for (i=0; i < obj.arity; i++) li.args[i] = ""; 468 | 469 | if (obj.arity == 1) { 470 | var span = document.createElement("SPAN"); 471 | span.innerHTML = obj.name + "("; 472 | li.appendChild(span); 473 | var arg = document.createElement("SPAN"); 474 | arg.innerHTML = "—"; 475 | li.appendChild(arg); 476 | makeArgDroppable(arg, li, 0); 477 | var span2 = document.createElement("SPAN"); 478 | span2.innerHTML = ")"; 479 | li.appendChild(span2); 480 | return; 481 | } 482 | 483 | // so we now have a binary operator or predicate 484 | 485 | if (obj.relationStyle) { 486 | var arg1 = document.createElement("SPAN"); 487 | arg1.innerHTML = "—"; 488 | li.appendChild(arg1); 489 | makeArgDroppable(arg1, li, 0); 490 | var span = document.createElement("SPAN"); 491 | span.innerHTML = " " + obj.name + " "; 492 | li.appendChild(span); 493 | var arg2 = document.createElement("SPAN"); 494 | arg2.innerHTML = "—"; 495 | li.appendChild(arg2); 496 | makeArgDroppable(arg2, li, 1); 497 | } else { 498 | var span = document.createElement("SPAN"); 499 | span.innerHTML = obj.name + "("; 500 | li.appendChild(span); 501 | var arg1 = document.createElement("SPAN"); 502 | arg1.innerHTML = "—"; 503 | li.appendChild(arg1); 504 | makeArgDroppable(arg1, li, 0); 505 | var span2 = document.createElement("SPAN"); 506 | span2.innerHTML = ", "; 507 | li.appendChild(span2); 508 | var arg2 = document.createElement("SPAN"); 509 | arg2.innerHTML = "—"; 510 | li.appendChild(arg2); 511 | makeArgDroppable(arg2, li, 1); 512 | var span3 = document.createElement("SPAN"); 513 | span3.innerHTML = ")"; 514 | li.appendChild(span3); 515 | } 516 | } 517 | // New exercise in the exercise window 518 | 519 | function setExercise(exerciseButton) { 520 | 521 | // clear all existing elements 522 | clearElement("root-environment"); 523 | getElement("root-environment").appendChild(newHeading("Root environment")); 524 | 525 | clearElement("proof"); 526 | getElement("proof").numlines = 0; 527 | getElement("proof").hasCircularity = false; 528 | 529 | clearElement("deductionDesc"); 530 | getElement("deductionDesc").assumptions = []; 531 | clearElement("deductions"); 532 | clearElement("deductionFootnote"); 533 | 534 | clearElement("formula-window"); 535 | getElement("formula-window").appendChild(newHeading("Formulas")); 536 | 537 | var head = getElement("term-heading"); 538 | 539 | clearElement("term-window"); 540 | getElement("term-window").appendChild(head); 541 | 542 | clearElement("predicates-list"); 543 | clearElement("operators-list"); 544 | 545 | var exerciseText = getElement("exercise"); 546 | 547 | var exerciseDesc = document.createElement("DIV"); 548 | exerciseDesc.id = "exercise-desc"; 549 | 550 | var exercise = exerciseButton.exercise; 551 | 552 | // Display exercise text 553 | exerciseDesc.innerHTML = "

Section " + exerciseButton.sectionTitle + "

"; 554 | if (exercise.name == exercise.law.name) 555 | exerciseDesc.innerHTML += "" + exercise.name + ": " + exercise.law.string; 556 | else 557 | exerciseDesc.innerHTML += "" + exercise.name + " (" + exercise.law.name + "): " + exercise.law.string; 558 | 559 | exerciseText.innerHTML = ""; 560 | 561 | exerciseText.appendChild(exerciseDesc); 562 | 563 | if (exercise.bestLength > 0) { 564 | exerciseText.appendChild(document.createElement("BR")); 565 | var span = document.createElement("SPAN"); 566 | span.appendChild(document.createTextNode("Shortest known non-circular proof: " + exercise.bestLength + " lines.")); 567 | exerciseText.appendChild(span); 568 | if (completedAllExercises()) { 569 | if (exercise.proof != "") { 570 | 571 | var subnode = document.createElement("OL"); 572 | subnode.innerHTML = exercise.proof; 573 | 574 | var button = newCollapseButton(subnode, true); 575 | 576 | span.appendChild(button); 577 | span.appendChild(subnode); 578 | 579 | } 580 | } 581 | } 582 | // Display exercise notes 583 | if (exercise.notes != "") { 584 | exerciseText.appendChild(document.createElement("BR")); 585 | var notes = document.createElement("DIV"); 586 | notes.innerHTML = exercise.notes; 587 | exerciseText.appendChild(notes); 588 | } 589 | 590 | // Load all given hypotheses 591 | exercise.law.givens.forEach( function(item) { 592 | if (item.type == "sentence in environment" || item.type == "environment") 593 | given(item); 594 | }); 595 | 596 | exerciseText.exercise = exercise; 597 | exerciseText.exerciseButton = exerciseButton; 598 | 599 | // reveal the formula window, if this option is active 600 | if (exercise.revealFormulaWindow) reveal("formula-window", "Formulas window"); 601 | // reveal the term window, if this option is active 602 | if (exercise.revealTermWindow) reveal("term-window", "Terms window"); 603 | // reveal the bound button, if this option is active 604 | if (exercise.revealBoundButton) reveal("bound-variable-button", "New bound variables button"); 605 | // reveal the operators window, if this option is active 606 | if (exercise.revealOperatorsWindow) reveal("operators-window", "Predicates and Operators window"); 607 | 608 | // Unlock any laws that were unlocked by the exercise 609 | exercise.newLaws.forEach( function(item) { 610 | unlock(item, "UNLOCKED"); 611 | }); 612 | 613 | // add true and false to formula windows, if revealed 614 | 615 | if (exercise.revealTrueFalse) 616 | { 617 | revealTrueFalse = true; 618 | achieve("UNLOCKED TRUE and FALSE formulas."); 619 | if (localStorage) { 620 | localStorage.setItem( "true false", "unlocked"); 621 | } 622 | } 623 | 624 | if (revealTrueFalse) { 625 | addContext(formulaContext(TRUE())); 626 | addContext(formulaContext(FALSE())); 627 | } 628 | 629 | 630 | // add each primitive to the formula window 631 | 632 | var primitives = listPrimitives(exercise.law, true, false, false, false,false,false,false, false); 633 | primitives.forEach( function(name) { 634 | addContext(formulaContext(name)); 635 | }); 636 | 637 | // add each free and bound var and primitive term to the term window 638 | 639 | var Vars = listPrimitives(exercise.law, false, true, true, true,false,false,false, false); 640 | Vars.forEach( function(name) { 641 | addContext(termContext(name)); 642 | }); 643 | 644 | // add each predicate and operator to the operators window 645 | 646 | var Predicates = listPrimitives(exercise.law, false,false,false,false,true,true,false, false); 647 | Predicates.forEach( function(name) { 648 | addPredicate(name); 649 | }); 650 | 651 | 652 | 653 | colorExerciseButton(exercise.button, true); 654 | 655 | 656 | // can't undo with only the given hypotheses 657 | getElement("undo-button").canUndo = false; 658 | } 659 | 660 | // create the root environment window 661 | 662 | function createRootEnvironment() { 663 | var box = getElement("root-environment"); 664 | 665 | box.style.margin = "5px"; 666 | box.style.padding = "0px 10px"; 667 | box.style.border = "1px solid black"; 668 | box.style.backgroundColor = "#eeeeee"; 669 | 670 | box.setAttribute('ondragover', "allowDrop(event)"); 671 | box.setAttribute('ondrop', "drop(event)"); 672 | box.setAttribute('onclick', "clickBox(this,event)"); 673 | box.type = "environment"; 674 | box.appendChild(newHeading("Root environment")); 675 | return box; 676 | } 677 | 678 | // returns the subenvironment of env with assumption in it, or false otherwise 679 | 680 | function findAssumption(env,assumption) { 681 | var i; 682 | 683 | for (i=0; i < env.children.length; i++) 684 | { 685 | var child = env.children[i]; 686 | if (child.type == "environment") 687 | if (child.assumption.name == assumption.name) { 688 | return child; 689 | } 690 | } 691 | 692 | return false; 693 | } 694 | 695 | // inside an existing environment, add an assumption subenvironment 696 | 697 | function makeAssumption(env, obj) { 698 | var assumption = toAssumption(obj); 699 | var box = findAssumption(env,assumption); 700 | 701 | if (box != false) return box; 702 | 703 | box = newBox(); 704 | box.id = uniqueId(); 705 | box.type = "environment"; 706 | box.style.backgroundColor = "#eeeeee"; 707 | 708 | box.setAttribute('onclick', "clickBox(this,event)"); 709 | 710 | 711 | env.appendChild(box); 712 | box.appendChild(newHeading(assumption.name)); 713 | box.assumption = assumption; 714 | 715 | // add box to the list of things removed by an immediate undo 716 | getElement("undo-button").deletionList.push(box); 717 | 718 | return box; 719 | } 720 | 721 | // when creating child environments, remember to set box.type to "environment" and box.assumption to the sentence being assumed. 722 | 723 | // create formula window 724 | 725 | function createFormulaWindow() { 726 | var box = getElement("formula-window"); 727 | 728 | box.style.display = 'none'; 729 | box.style.margin = "5px"; 730 | box.style.padding = "0px 10px"; 731 | box.style.border = "1px solid black"; 732 | box.style.backgroundColor = "#eeeeee"; 733 | 734 | box.setAttribute('ondragover', "allowDrop(event)"); 735 | box.setAttribute('ondrop', "drop(event)"); 736 | box.type = "formula window"; 737 | box.appendChild(newHeading("Formulas")); 738 | return box; 739 | } 740 | 741 | // create term window 742 | 743 | function createTermWindow() { 744 | var box = getElement("term-window"); 745 | 746 | box.style.display = 'none'; 747 | box.style.margin = "5px"; 748 | box.style.padding = "0px 10px"; 749 | box.style.border = "1px solid black"; 750 | box.style.backgroundColor = "#eeeeee"; 751 | 752 | box.setAttribute('ondragover', "allowDrop(event)"); 753 | box.setAttribute('ondrop', "drop(event)"); 754 | box.type = "term window"; 755 | var div = document.createElement("DIV"); 756 | div.appendChild(newHeading("Terms")); 757 | div.id = "term-heading"; 758 | box.appendChild(div); 759 | 760 | var freeButton = newButton("New free variable"); 761 | div.appendChild(freeButton); 762 | freeButton.onclick = freeButtonClick; 763 | 764 | var boundButton = newButton("New bound variable"); 765 | boundButton.style.display = 'none'; 766 | boundButton.id = "bound-variable-button"; 767 | div.appendChild(boundButton); 768 | boundButton.onclick = boundButtonClick; 769 | 770 | return box; 771 | } 772 | 773 | // add a free variable to the term window 774 | function freeButtonClick() { 775 | var i; 776 | var num=0; 777 | var box = getElement("term-window"); 778 | 779 | do 780 | { 781 | var str = FreeVariableName(num); 782 | var match = false; 783 | 784 | for (i=0; i < box.children.length; i++) 785 | { 786 | var child = box.children[i]; 787 | if (child.type == "termBox") { 788 | if (child.term.subtype == "free variable") { 789 | if (child.term.argList[0].subtype == str) { 790 | match = true; 791 | } 792 | } 793 | } 794 | } 795 | num++; 796 | } 797 | while (match); 798 | 799 | addContext(termContext(new FreeVariable(str))); 800 | } 801 | 802 | // add a bound variable to the term window 803 | function boundButtonClick() { 804 | var i; 805 | var num=0; 806 | var box = getElement("term-window"); 807 | 808 | do { 809 | var str = BoundVariableName(num); 810 | var match = false; 811 | 812 | for (i=0; i < box.children.length; i++) { 813 | var child = box.children[i]; 814 | if (child.type == "termBox") { 815 | if (child.term.subtype == "bound variable") { 816 | if (child.term.argList[0].subtype == str) { 817 | match = true; 818 | } 819 | } 820 | } 821 | } 822 | num++; 823 | } while (match); 824 | 825 | addContext(termContext(new BoundVariable(str))); 826 | } 827 | 828 | 829 | function createOperatorsWindow() { 830 | var box = getElement("operators-window"); 831 | box.style.display = 'none'; 832 | 833 | var div; 834 | var ul; 835 | 836 | div = document.createElement("DIV"); 837 | div.appendChild(newHeading("Predicates")); 838 | box.appendChild(div); 839 | 840 | ul = document.createElement("UL"); 841 | ul.id = "predicates-list"; 842 | box.appendChild(ul); 843 | 844 | div = document.createElement("DIV"); 845 | div.appendChild(newHeading("Operators")); 846 | box.appendChild(div); 847 | 848 | ul = document.createElement("UL"); 849 | ul.id = "operators-list"; 850 | box.appendChild(ul); 851 | return box; 852 | } 853 | 854 | 855 | 856 | // if previous session unlocked formula window, true/false, and/or term window, reveal it; also unlock all laws already unlocked 857 | function checkForUnlocks() { 858 | if (localStorage) { 859 | // the string form with spaces instead of hyphens is for backwards compatibility 860 | if (localStorage.getItem("formula window") == "unlocked" || localStorage.getItem("formula-window") == "unlocked" ) 861 | reveal("formula-window", "Formulas window"); 862 | 863 | if (localStorage.getItem("true false") == "unlocked" || localStorage.getItem("true-false") == "unlocked") { 864 | achieve("UNLOCKED TRUE and FALSE formulas."); 865 | revealTrueFalse = true; 866 | } 867 | 868 | if (localStorage.getItem("term window") == "unlocked" || localStorage.getItem("term-window") == "unlocked") 869 | reveal("term-window", "Terms window"); 870 | 871 | if (localStorage.getItem("operators window") == "unlocked" || localStorage.getItem("operators-window") == "unlocked") 872 | reveal("operators-window", "Predicates and Operators windows"); 873 | if (localStorage.getItem("bound variable button") == "unlocked" || localStorage.getItem("bound-variable-button") == "unlocked") 874 | reveal("bound-variable-button", "New bound variable button"); 875 | 876 | allLaws.forEach( function( law ) { 877 | // in legacy code, law is stored using the full name 878 | var str; 879 | 880 | str = localStorage.getItem("law " + law.name); 881 | 882 | if (str != null) { 883 | unlock(law, str); 884 | } 885 | 886 | // in current version, law is stored using short Name 887 | 888 | str = localStorage.getItem("law " + law.shortName); 889 | if (str != null) { 890 | unlock(law, str); 891 | } 892 | }); 893 | 894 | } 895 | 896 | // ensure legacy games from older versions still unlock solved exercises and the laws they in turn unlock 897 | exerciseList.forEach( function(exercise) { 898 | if (exercise.button.solved) 899 | if (localStorage) 900 | { 901 | unlock(exercise.law, "PROVED"); 902 | exercise.newLaws.forEach( function(item) { 903 | unlock(item, "UNLOCKED"); 904 | }); 905 | } 906 | }); 907 | 908 | } 909 | 910 | 911 | // unique ID generator for creating new objects 912 | function uniqueId() { 913 | return 'id-' + Math.random().toString(36).substr(2, 16); 914 | }; 915 | 916 | // create a new box with a sentence in it 917 | 918 | function newSentenceBox(sentence) { 919 | var box = newBox(); 920 | box.innerHTML = sentence.name; 921 | box.setAttribute('draggable', true); 922 | box.setAttribute('ondragstart', "drag(event)"); 923 | box.setAttribute('onclick', "clickBox(this,event)"); 924 | box.id = uniqueId(); 925 | box.type = "sentenceBox"; 926 | box.sentence = sentence; 927 | return box; 928 | }; 929 | 930 | 931 | // add a formula to the formula window, or a sentence in environment to the relevant environment 932 | 933 | // convert a list of assumptions into an environment box 934 | function getEnvironment(list) { 935 | var env = getElement("root-environment"); 936 | list.forEach( function(statement) { 937 | env = makeAssumption(env, statement); 938 | }); 939 | return env; 940 | } 941 | 942 | function addContext(context) { 943 | 944 | 945 | if (context.type == "environment") 946 | { 947 | // getEnvironment will create the environment if it does not already exist 948 | getEnvironment(context.environment); 949 | return; 950 | } 951 | 952 | if (context.type != "formula" && context.type != "term context" && context.type != "sentence in environment") { 953 | error("Cannot add this type of context: " + context.type); 954 | return; 955 | } 956 | 957 | var box = newBox(); 958 | 959 | box.setAttribute('draggable', true); 960 | box.setAttribute('ondragstart', "drag(event)"); 961 | box.setAttribute('onclick', "clickBox(this,event)"); 962 | box.id = uniqueId(); 963 | 964 | if (context.type == "term context") { 965 | box.term = context.term; 966 | box.innerHTML = context.term.name; 967 | } 968 | else{ 969 | box.sentence = context.sentence; 970 | box.innerHTML = context.sentence.name; 971 | } 972 | 973 | if (context.type == "formula") { 974 | box.type = "formulaBox"; 975 | var nodes = getElement("formula-window").childNodes; 976 | var i; 977 | 978 | for (i=0; i < nodes.length; i++) 979 | if (nodes[i].type == "formulaBox") 980 | if (nodes[i].sentence.name == box.sentence.name) { 981 | notify("The formula " + box.sentence.name + " is already present in this window."); 982 | return box; 983 | } 984 | getElement("formula-window").appendChild(box); 985 | } else if (context.type == "term context") { 986 | box.type = "termBox"; 987 | var nodes = getElement("term-window").childNodes; 988 | var i; 989 | 990 | for (i=0; i < nodes.length; i++) 991 | if (nodes[i].type == "termBox") 992 | if (nodes[i].term.name == box.term.name) { 993 | notify("The term " + box.term.name + " is already present in this window."); 994 | return box; 995 | } 996 | 997 | getElement("term-window").appendChild(box); 998 | } 999 | else { 1000 | var env = getEnvironment(context.environment); 1001 | box.type = "sentenceBox"; 1002 | 1003 | var nodes = env.childNodes; 1004 | var i; 1005 | 1006 | for (i=0; i < nodes.length; i++) 1007 | if (nodes[i].type == "sentenceBox") 1008 | if (nodes[i].sentence.name == box.sentence.name) 1009 | { 1010 | notify("The sentence " + box.sentence.name + " is already present in this window."); 1011 | return box; 1012 | } 1013 | 1014 | env.appendChild(box); 1015 | } 1016 | 1017 | // add box to the list of things that can be deleted by undo button 1018 | getElement("undo-button").deletionList.push(box); 1019 | 1020 | return box; 1021 | } 1022 | 1023 | // checks if all exercises have been completed 1024 | 1025 | function completedAllExercises() { 1026 | return exerciseButtons.every( function(button) { return button.solved; } ); 1027 | } 1028 | 1029 | // checks if exactly one exercise has been completed 1030 | 1031 | function completedOneExercise() { 1032 | var i = 0; 1033 | 1034 | exerciseButtons.forEach( function (button) { if (button.solved) i++; }); 1035 | 1036 | return (i==1); 1037 | } 1038 | 1039 | 1040 | // adds a collapsible proof (in HTML format) to the element node 1041 | 1042 | function listProof( node, proof ) { 1043 | 1044 | var subnode = document.createElement("OL"); 1045 | subnode.innerHTML = proof; 1046 | 1047 | var button = newCollapseButton(subnode, true); 1048 | 1049 | node.appendChild(button); 1050 | node.appendChild(subnode); 1051 | } 1052 | 1053 | // correctly color the exercise button (and also the exercise description and proof window, if window is true) 1054 | 1055 | function colorExerciseButton(exerciseButton, windows) 1056 | { 1057 | var exercise = exerciseButton.exercise; 1058 | var len = exercise.personalBest; 1059 | 1060 | if (exerciseButton.solved) 1061 | { 1062 | if (len > exercise.bestLength) { 1063 | exerciseButton.style.backgroundColor = 'blue'; 1064 | exerciseButton.style.color = 'white'; 1065 | exerciseButton.style.cursor = 'pointer'; 1066 | if (windows) { 1067 | getElement("exercise-desc").style.backgroundColor = 'aqua'; 1068 | getElement("proof-box").style.backgroundColor = 'aqua'; 1069 | } 1070 | return; 1071 | } 1072 | if (len == exercise.bestLength) { 1073 | exerciseButton.style.backgroundColor = 'hsl(150,50%,50%)'; 1074 | exerciseButton.style.color = 'white'; 1075 | exerciseButton.style.cursor = 'pointer'; 1076 | if (windows) { 1077 | getElement("exercise-desc").style.backgroundColor = 'lightgreen'; 1078 | getElement("proof-box").style.backgroundColor = 'lightgreen'; 1079 | } 1080 | return; 1081 | } 1082 | if (len < exercise.bestLength) { 1083 | exerciseButton.style.backgroundColor = 'lime'; 1084 | exerciseButton.style.color = 'white'; 1085 | exerciseButton.style.cursor = 'pointer'; 1086 | if (windows) { 1087 | getElement("exercise-desc").style.backgroundColor = 'greenyellow'; 1088 | getElement("proof-box").style.backgroundColor = 'greenyellow'; 1089 | } 1090 | return; 1091 | } 1092 | } 1093 | 1094 | if (exercise.activated) { 1095 | exerciseButton.style.backgroundColor = 'hsl(60,100%,75%)'; 1096 | exerciseButton.style.color = "black"; 1097 | exerciseButton.style.cursor = "pointer" 1098 | if (windows) { 1099 | getElement("exercise-desc").style.backgroundColor = 'yellow'; 1100 | getElement("proof-box").style.backgroundColor = 'yellow'; 1101 | } 1102 | return; 1103 | } 1104 | 1105 | exerciseButton.style.backgroundColor = 'hsl(0,10%,75%)'; 1106 | exerciseButton.style.color = 'white'; 1107 | exerciseButton.style.cursor = 'not-allowed'; 1108 | 1109 | } 1110 | 1111 | // make a deduction; check for victory condition 1112 | 1113 | function deduce(conclusion, justification, law) { 1114 | 1115 | getElement("undo-button").canUndo = true; 1116 | getElement("undo-button").deletionList = []; // list of elements to be deleted on undo 1117 | getElement("undo-button").numlines = getElement("proof").numlines; 1118 | getElement("undo-button").hasCircularity = getElement("proof").hasCircularity; 1119 | 1120 | 1121 | // add conclusion to either a deduction environment or the formula window, as appropriate. 1122 | addContext(conclusion); 1123 | 1124 | // creating a formula or term is not important enough to mention explicitly in the proof, nor should it trigger a victory condition. 1125 | if (conclusion.type == "formula" || conclusion.type == "term context") return; 1126 | 1127 | var justificationSentences = justification.filter( function(context) { return (context.type == "sentence in environment"); }); 1128 | 1129 | var name = law.name; 1130 | 1131 | if (getElement("exercise").exercise != "") 1132 | if (law.index >= getElement("exercise").exercise.law.index) { 1133 | name += "*"; 1134 | getElement("proof").hasCircularity = true; 1135 | } 1136 | 1137 | appendToProof( deductionString("From", justificationSentences, conclusion) + " [" + name + "]"); 1138 | 1139 | 1140 | 1141 | 1142 | var exercise = getElement("exercise").exercise; 1143 | var exerciseButton = getElement("exercise").exerciseButton; 1144 | 1145 | if (exercise != "") 1146 | if (conclusion.name == exercise.law.conclusion.name) 1147 | { 1148 | // hooray, you solved it! Now one can't undo it. 1149 | getElement("undo-button").canUndo = false; 1150 | 1151 | if (!exerciseButton.solved) { 1152 | appendToProof('QED!'); 1153 | unlock(exercise.law, "PROVED"); 1154 | exerciseButton.solved = true; 1155 | 1156 | if (localStorage) 1157 | localStorage.setItem(exercise.name, "solved"); 1158 | exercise.newExercises.forEach( function(item) { 1159 | activateExerciseButton(item, false); 1160 | }); 1161 | 1162 | if (completedOneExercise()) 1163 | alert('Congratulations, you solved your first exercise! Now two more exercises will be unlocked, as well as the next section of the text. (For subsequent exercises, we will notify you of an exercise being solved by changing the color of the exercise and its proof to either green or blue, depending on whether you found the shortest known proof or not. We also add a QED symbol (standing for "quod erat demonstrandum", or "what was to be demonstrated") to the end of the proof.)'); 1164 | 1165 | if (completedAllExercises()) { 1166 | alert("Congratulations, you completed all the exercises! You are now a master of propositional and first-order logic! The next time one clicks on an exercise, one should now see a button next to the shortest length proof message which, when clicked, will reveal the shortest known proof for that exercise."); 1167 | achieve("COMPLETED all the exercises!"); 1168 | } 1169 | } else { 1170 | appendToProof('QED! (again)'); 1171 | unlock(exercise.law, "PROVED"); // this is to give legacy games from older versions a chance to re-unlock the exercise 1172 | } 1173 | 1174 | var len = getElement("proof").numlines; 1175 | var node; 1176 | var proof = getElement("proof").innerHTML; 1177 | 1178 | if (getElement("proof").hasCircularity) { 1179 | notify(exercise.name + " was proved in " + len + " lines, using laws obtained after the exercise was first solved."); 1180 | } 1181 | else { 1182 | if (localStorage) { 1183 | var oldlen = localStorage.getItem("lines " + exercise.name); 1184 | if (oldlen == undefined) 1185 | { 1186 | node = notify(exercise.name + " was proved in " + len + " lines."); 1187 | localStorage.setItem("lines " + exercise.name, len); 1188 | localStorage.setItem("proof " + exercise.name, proof); 1189 | } else if (oldlen > len) { 1190 | node = notify(exercise.name + " was reproved in " + len + " lines. A new personal best!"); 1191 | localStorage.setItem("lines " + exercise.name, len); 1192 | localStorage.setItem("proof " + exercise.name, proof); 1193 | } else { 1194 | node = notify(exercise.name + " was reproved in " + len + " lines."); 1195 | } 1196 | } 1197 | else 1198 | node = notify(exercise.name + " was proved in " + len + " lines."); 1199 | 1200 | listProof( node, proof ); 1201 | 1202 | if (len < exercise.personalBest) exercise.personalBest = len; 1203 | 1204 | if (len == exercise.bestLength) 1205 | { 1206 | notify("You matched the record for the shortest known proof of " + exercise.name + "!"); 1207 | } 1208 | if (len < exercise.bestLength) 1209 | alert("You beat the record for the shortest known proof of " + exercise.name + "! Please send it to me at tao@math.ucla.edu and I will update the record (with an acknowledgment) in the next version of the text."); 1210 | } 1211 | 1212 | colorExerciseButton(exerciseButton, true); 1213 | } 1214 | } 1215 | 1216 | //// ExerciseButton //// 1217 | 1218 | function createExerciseButtonBox() { 1219 | var box = getElement("exercise-button-box"); 1220 | 1221 | var subnode = getElement("exercise-button-subbox"); 1222 | 1223 | var button = newCollapseButton(subnode, false); 1224 | 1225 | box.appendChild(button); 1226 | box.appendChild(document.createElement("BR")); 1227 | box.appendChild(subnode); 1228 | } 1229 | 1230 | function createExerciseButton( exercise) { 1231 | var button = newButton(exercise.shortName); 1232 | button.exercise = exercise; 1233 | button.className = "clickable"; 1234 | button.enabled = false; 1235 | button.solved = false; 1236 | button.style.width = "50px"; 1237 | button.style.paddingLeft = "0px"; 1238 | button.style.paddingRight = "0px"; 1239 | button.sectionTitle = sectionTitle; 1240 | 1241 | exercise.button = button; 1242 | 1243 | getElement("exercise-button-subbox").lastChild.lastChild.appendChild(button); 1244 | return button; 1245 | } 1246 | 1247 | function exerciseFromShortName(shortName) { 1248 | var args = exerciseData[shortName]; 1249 | if (!args) { 1250 | throw new Error("exercise shortName not found: " + shortName); 1251 | } 1252 | exerciseFromData(shortName, args); 1253 | } 1254 | 1255 | function newSection(section, name) { 1256 | var box = getElement("exercise-button-subbox"); 1257 | var div = document.createElement("DIV"); 1258 | div.style.clear = "both"; 1259 | div.className = "clearfix"; 1260 | 1261 | var span = document.createElement("SPAN"); 1262 | sectionTitle = section + ". " + name; 1263 | span.innerHTML = sectionTitle; 1264 | span.style.float = "left"; 1265 | span.style.clear = "left"; 1266 | span.style.width = "200px"; 1267 | var rightBox = document.createElement("SPAN"); 1268 | rightBox.style.float = "left"; 1269 | rightBox.style.clear = "right"; 1270 | div.appendChild(span); 1271 | div.appendChild(rightBox); 1272 | box.appendChild(div); 1273 | 1274 | // load all exercises starting with section 1275 | 1276 | Object.keys(exerciseData).forEach( function(key) { 1277 | if (key.indexOf(section + ".") == 0) 1278 | { 1279 | exerciseFromShortName(key); 1280 | } 1281 | }); 1282 | } 1283 | 1284 | 1285 | // fromStorage is true if one is activating the button because localStorage reports that it was previously activated. In which case we hide the "now available" notification. 1286 | 1287 | function activateExerciseButton(exercise, fromStorage) { 1288 | exercise.activated = true; 1289 | 1290 | var button = exercise.button; 1291 | 1292 | if (button.enabled) return; 1293 | button.enabled = true; 1294 | 1295 | button.onclick = function() { 1296 | setExercise(this); 1297 | }; 1298 | 1299 | if (!fromStorage) notify(exercise.name + " now available.") 1300 | exerciseButtons.push(button); 1301 | if (localStorage) { 1302 | if (localStorage.getItem(exercise.name) == undefined) { 1303 | localStorage.setItem(exercise.name, "unlocked"); 1304 | } 1305 | 1306 | var len = localStorage.getItem("lines " + exercise.name); 1307 | if (len != undefined) { 1308 | var node = notify(exercise.name + " was proven in " + len + " lines."); 1309 | var proof = localStorage.getItem("proof " + exercise.name); 1310 | listProof(node, proof); 1311 | exercise.personalBest = len; 1312 | button.solved = true; 1313 | } 1314 | } 1315 | 1316 | colorExerciseButton(button, false); 1317 | 1318 | return button; 1319 | } 1320 | 1321 | // create the box of available deductions 1322 | 1323 | function createDeductionsBox() { 1324 | var box = getElement("deductions-box"); 1325 | box.style.margin = "5px"; 1326 | box.style.padding = "0px 10px"; 1327 | 1328 | box.appendChild(newHeading("Available deductions")); 1329 | 1330 | var desc = document.createElement("DIV"); 1331 | desc.id = "deductionDesc"; 1332 | box.appendChild(desc); 1333 | 1334 | var list = document.createElement("OL"); 1335 | list.id = "deductions"; 1336 | box.appendChild(list); 1337 | 1338 | var footnote = document.createElement("DIV"); 1339 | footnote.id = "deductionFootnote"; 1340 | box.appendChild(footnote); 1341 | } 1342 | 1343 | // list the assumptions used when searching for deductions 1344 | 1345 | function from( assumptions ) 1346 | { 1347 | var desc = getElement("deductionDesc"); 1348 | 1349 | var shortStr = "From "; 1350 | var longStr = "From "; 1351 | 1352 | getElement("undo-button").canUndo = false; // there was an exploit where one set up a deduction, undid the hypothesis enabling that deduction, and then executed the deduction anyway, to save a proof line step 1353 | 1354 | assumptions.forEach( function( assumption ) { 1355 | shortStr = longStr + toContext(assumption).name; 1356 | longStr = shortStr + ", "; 1357 | }); 1358 | 1359 | desc.innerHTML = shortStr + ":"; 1360 | desc.assumptions = assumptions; 1361 | 1362 | clearElement("deductions"); 1363 | clearElement("deductionFootnote"); 1364 | } 1365 | 1366 | 1367 | // add a line to available deductions 1368 | 1369 | function appendToDeductions(output, justification, law) { 1370 | var proof = getElement("deductions"); 1371 | var name = law.name; 1372 | 1373 | if (getElement("exercise").exercise != "") 1374 | if (law.index >= getElement("exercise").exercise.law.index) { 1375 | // we have circularity! 1376 | if (hideCircularLaws) return false; 1377 | name += "*"; 1378 | proof.hasCircularity = true; 1379 | } 1380 | 1381 | if (output.multivalued) { 1382 | var i; 1383 | for (i=0; i < output.conclusions.length; i++) 1384 | appendConclusion(output.conclusions[i], justification, output.illegal, name, law); 1385 | 1386 | } 1387 | else { 1388 | appendConclusion(output.conclusion, justification, output.illegal, name, law); 1389 | } 1390 | 1391 | return true; 1392 | } 1393 | 1394 | 1395 | 1396 | 1397 | // unlock a law, make it available for use in future deductions 1398 | 1399 | function unlock(law, text) { 1400 | if (law.unlocked) return; // prevent duplicate unlocking 1401 | law.unlocked = true; 1402 | unlockedLaws.push(law); 1403 | achieve("" + text + " " + ""+ law.name+": " + law.string); 1404 | 1405 | if (localStorage) 1406 | localStorage.setItem("law " + law.shortName, text); 1407 | 1408 | // If the law has no environment but produces a conclusion in the root environment, add a version of the law in which the environment is relative. 1409 | 1410 | if (law.clone != "") 1411 | { 1412 | unlock(law.clone, text); 1413 | } 1414 | } 1415 | 1416 | 1417 | // append a single conclusion to the deductions list with justification "justification" and law name "name". If "illegal", silver out the deduction 1418 | 1419 | function appendConclusion(conclusion, justification, illegal, name, law) { 1420 | var proof = getElement("deductions"); 1421 | var node = document.createElement("LI"); 1422 | var span = document.createElement("SPAN"); 1423 | 1424 | span.innerHTML = "" + name + ":"; 1425 | node.appendChild(span); 1426 | 1427 | if (isLegal(conclusion) && !illegal) 1428 | { 1429 | var button = newButton( conclusion.name); 1430 | button.conclusion = conclusion; 1431 | button.justification = justification; 1432 | button.onclick = function() { 1433 | if (this == lastClickedButton) return; // prevent double click from doing anything 1434 | lastClickedButton = this; 1435 | deduce(this.conclusion, this.justification, law); 1436 | }; 1437 | node.button = button; 1438 | node.isLegal = true; 1439 | node.appendChild(button); 1440 | } 1441 | else { 1442 | node.isLegal = false; 1443 | var span2 = document.createElement("SPAN"); 1444 | span2.innerHTML = conclusion.name; 1445 | span2.style.color = "silver"; 1446 | node.appendChild(span2); 1447 | proof.hasIllFormed = true; 1448 | } 1449 | proof.appendChild(node); 1450 | } 1451 | 1452 | 1453 | 1454 | 1455 | // Exercise object 1456 | 1457 | function makeExerciseName(shortName) { 1458 | return "EXERCISE " + shortName; 1459 | } 1460 | 1461 | // Register an exercise from givens and conclusion, or from the shortname of an existing law 1462 | 1463 | function exerciseFromData(shortName, args) { 1464 | var law; 1465 | 1466 | // relying here on the fact that different input types have different length; probably not the most proper solution 1467 | switch(args.length) { 1468 | case 1: 1469 | law = lawsByShortName[args[0]]; 1470 | break; 1471 | case 2: 1472 | law = new Law(shortName, makeExerciseName(shortName), args[0], args[1]); 1473 | break; 1474 | case 4: // in this case we also supply an alternate givens/conclusion pair as a matching template 1475 | law = new Law(shortName, makeExerciseName(shortName), args[0], args[1]); 1476 | law.givensTemplate = []; 1477 | args[2].forEach( function(given) { 1478 | law.givensTemplate.push(toContext(given)); 1479 | }); 1480 | law.conclusionTemplate = toContext(args[3]); 1481 | if (law.clone != "") { 1482 | var givensTemplateClone = law.givensTemplate.slice(0); 1483 | givensTemplateClone.push(rootEnvironmentContext()); 1484 | law.clone.givensTemplate = givensTemplateClone; 1485 | law.clone.conclusionTemplate = law.conclusionTemplate; 1486 | } 1487 | break; 1488 | } 1489 | 1490 | // We don't actually need to return the object. 1491 | new Exercise(shortName, law); 1492 | } 1493 | 1494 | function Exercise(shortName, law) { 1495 | if (!(law instanceof Law)) { 1496 | throw new Error('law argument is not a Law object: ' + law); 1497 | } 1498 | 1499 | this.shortName = shortName; 1500 | this.name = makeExerciseName(shortName); 1501 | this.law = law; 1502 | 1503 | maxLawIndex++; 1504 | this.law.index = maxLawIndex; 1505 | 1506 | if (this.law.clone != "") { 1507 | this.law.clone.index = this.law.index; 1508 | } 1509 | 1510 | 1511 | this.newLaws = []; // an array of laws unlocked when exercise is attempted(can be empty) 1512 | this.newExercises = []; // an array of exercises unlocked when exercise is completed (empty by default) 1513 | this.completionMsg = ""; // message upon completion of exercise (default empty) 1514 | this.notes = ""; // notes to make when an exercise is set 1515 | this.revealFormulaWindow = false; // do we reveal the formula window when this exercise opens? 1516 | this.revealTermWindow = false; // do we reveal the term window when this exercise opens? 1517 | this.revealTrueFalse = false; // do we populate formulas window with true and false? 1518 | this.revealBoundButton = false; // do we reveal the new Bound var button? 1519 | this.revealOperatorsWindow = false; // do we reveal the operators window? 1520 | this.bestLength = 1000; // the shortest length proof I know of (w/o cheating) 1521 | this.proof = ""; // the innerHTML of the best proof 1522 | this.personalBest = 1000; // personal best length for proof 1523 | this.activated = false; // whether this exercise has been unlocked 1524 | 1525 | 1526 | this.unlocks = function( law ) { 1527 | this.newLaws.push(law); 1528 | law.index = this.law.index - 1; // this can cause some laws to share the same index, but this is OK 1529 | 1530 | if (law.clone != "") { 1531 | law.clone.index = law.index; 1532 | } 1533 | }; 1534 | 1535 | this.button = createExerciseButton(this); 1536 | 1537 | if (localStorage) { 1538 | var str = localStorage.getItem(this.name); 1539 | 1540 | if (str == "unlocked" || str == "solved") { 1541 | activateExerciseButton(this, true); 1542 | if (str == "solved") { 1543 | this.solved = true; 1544 | this.button.solved = true; 1545 | this.personalBest = localStorage.getItem("lines " + this.name); 1546 | } 1547 | } 1548 | } 1549 | 1550 | this.unlockedBy = function( prerequisite ) { 1551 | prerequisite.newExercises.push(this); 1552 | if (prerequisite.button.solved) 1553 | { 1554 | // this is needed if the dependency graph for the text has updated and one doesn't want to reset the text 1555 | activateExerciseButton(this, false); 1556 | } 1557 | }; 1558 | 1559 | exerciseList.push(this); 1560 | exercisesByShortName[shortName] = this; 1561 | } 1562 | 1563 | 1564 | 1565 | 1566 | ////////////////////// ACTIONS ////////////////// 1567 | 1568 | // ev.target is not always the box or environment that one wishes to manipulate due to the fact that one may have dropped onto a suboject. So one has to pop up until one has a valid object 1569 | 1570 | function correctTarget(ev) { 1571 | var targ = ev.target; 1572 | 1573 | if (targ == undefined) return targ; // nothing can be done in this case 1574 | if (targ == null) return targ; // nothing can be done in this case 1575 | 1576 | while (targ.type != "environment" && targ.type != "sentenceBox" && targ.type != "formulaBox" && targ.type != "formula window" && targ.type != "termBox") 1577 | targ = targ.parentElement; 1578 | 1579 | return targ; 1580 | } 1581 | 1582 | // click a sentence box 1583 | 1584 | function clickBox(box,event) { 1585 | 1586 | // stop parent box from also triggering 1587 | event.stopPropagation(); 1588 | 1589 | // ctrl-click (or meta-click for macOS) adds an assumption to the current list 1590 | if (event.ctrlKey || event.metaKey) 1591 | { 1592 | var assumptions = getElement("deductionDesc").assumptions; 1593 | if (assumptions == undefined) assumptions = []; 1594 | assumptions.push(toContext(box)); 1595 | getElement("deductionDesc").box = box; 1596 | makeMatches(assumptions); 1597 | } 1598 | else { 1599 | getElement("deductionDesc").box = box; 1600 | makeMatches([toContext(box)]); 1601 | } 1602 | } 1603 | 1604 | 1605 | // find and list all the deductions that can be made from an array of hypotheses 1606 | 1607 | function makeMatches(justification) { 1608 | // list the justifications at the top of the Available deductions window 1609 | from( justification ); 1610 | 1611 | // if there are precisely two justifications, also consider what can be derived from their reversal. In principle one could extend this to the case of three or more justifications, but I didn't do so. 1612 | var revJustification; 1613 | if (justification.length == 2) 1614 | revJustification = [justification[1], justification[0]]; 1615 | 1616 | var numMatches = 0; 1617 | var proof = getElement("deductions"); 1618 | proof.hasCircularity = false; 1619 | proof.hasIllFormed = false; 1620 | 1621 | 1622 | unlockedLaws.forEach( function(law) { 1623 | 1624 | // collect: free variables, bound variables, primitive terms, atomic sentences 1625 | 1626 | var primitives = listPrimitives(law, false, true, true, true,false,false,true, true); 1627 | 1628 | var output = matchWithGivens( justification, law, primitives); 1629 | 1630 | if (output.matches) { 1631 | if (appendToDeductions(output, justification, law)) 1632 | numMatches++; 1633 | } 1634 | 1635 | if (justification.length == 2) { 1636 | output = matchWithGivens( revJustification, law, primitives); 1637 | 1638 | if (output.matches) { 1639 | if (appendToDeductions(output, revJustification, law)) 1640 | numMatches++; 1641 | } 1642 | } 1643 | }); 1644 | 1645 | if (numMatches == 0) { 1646 | var node = document.createElement("LI"); 1647 | var span = document.createElement("SPAN"); 1648 | span.innerHTML = "No available deductions can be formed from this selection."; 1649 | node.appendChild(span); 1650 | proof.appendChild(node); 1651 | } 1652 | 1653 | var footnote = getElement("deductionFootnote"); 1654 | 1655 | if (proof.hasCircularity) 1656 | { 1657 | footnote.innerHTML = "* These rules occur in the text at or after the current exercise. While valid for use in proofs, they will not qualify for shortest proof records. Press 'c' to toggle availability of such rules."; 1658 | } 1659 | if (proof.hasIllFormed) { 1660 | if (footnote.innerHTML != "") footnote.innerHTML += "
"; 1661 | footnote.innerHTML += 'Deductions in silver cannot be made, as either the conclusion or a term used are not well formed in the indicated environment (e.g., because they use free variables that are not available in that environment, or have nested quantifications over the same bound variable).'; 1662 | } 1663 | } 1664 | 1665 | // remember the ID of the object being dragged. 1666 | 1667 | function drag(ev) { 1668 | ev.dataTransfer.setData("text", ev.target.id); 1669 | } 1670 | 1671 | 1672 | function allowDrop(ev) { 1673 | ev.preventDefault(); 1674 | } 1675 | 1676 | function drop(ev) { 1677 | ev.preventDefault(); 1678 | 1679 | // arg1 is the object that is being dragged. 1680 | var arg1 = getElement(ev.dataTransfer.getData("text")); 1681 | var arg2 = correctTarget(ev); 1682 | 1683 | if (arg1 == null) return; // this can happen for instance if one drags a selection 1684 | if (arg1.type == undefined) return; // this can happen for instance if one drags a selection 1685 | 1686 | if (arg2.type == "formula window") 1687 | { 1688 | // dragging a sentence box to the formula window creates a new formula 1689 | if (arg1.type == "sentenceBox") { 1690 | addContext(formulaContext(arg1.sentence)); 1691 | return; 1692 | } 1693 | return; 1694 | } 1695 | 1696 | 1697 | makeMatches([toContext(arg1), toContext(arg2)]); 1698 | } 1699 | 1700 | 1701 | // use keyboard numbers for the first 9 links in deduction menu 1702 | 1703 | function keydown(event) { 1704 | 1705 | if (event.key == 'u' || event.key == 'U') 1706 | getElement("undo-button").onclick(); 1707 | 1708 | if (event.key == 'r' || event.key == 'R') 1709 | getElement("restart-button").onclick(); 1710 | 1711 | if (event.key == 'l') { 1712 | 1713 | unlockedLaws.forEach( function(law) { 1714 | debug("law " + law.name + " has index " + law.index); 1715 | }); 1716 | } 1717 | 1718 | if (event.key == '<') 1719 | getElement("prev-exercise").onclick(); 1720 | 1721 | if (event.key == '>') 1722 | getElement("next-exercise").onclick(); 1723 | 1724 | if (event.key == 'c') { 1725 | hideCircularLaws = !hideCircularLaws; 1726 | makeMatches(getElement("deductionDesc").assumptions); 1727 | } 1728 | 1729 | // click the first unsolved exercise, if such exists 1730 | if (event.key == 'n') { 1731 | var i; 1732 | for (i=0; i < exerciseList.length; i++) { 1733 | if (exerciseList[i].button.enabled && !exerciseList[i].button.solved) { 1734 | exerciseList[i].button.onclick(); 1735 | return; 1736 | } 1737 | } 1738 | } 1739 | 1740 | // click the last unsolved exercise, if such exists 1741 | if (event.key == 'N') { 1742 | var i; 1743 | for (i=exerciseList.length; i--;) { 1744 | if (exerciseList[i].button.enabled && !exerciseList[i].button.solved) { 1745 | exerciseList[i].button.onclick(); 1746 | return; 1747 | } 1748 | } 1749 | } 1750 | 1751 | // delete selected item, if it is not the root environment 1752 | 1753 | if (event.key == 'Delete') { 1754 | var assumptions = getElement("deductionDesc").assumptions; 1755 | if (assumptions.length != 1) return; 1756 | var box = getElement("deductionDesc").box; 1757 | if (box == getElement("root-environment")) { 1758 | notify("Cannot delete root environment."); 1759 | return; 1760 | } 1761 | getElement("undo-button").canUndo = false; // can't undo a delete 1762 | box.remove(); 1763 | } 1764 | 1765 | var num = parseInt(event.key); 1766 | 1767 | if (num >= 1 && num <= 9) { 1768 | var items = getElement("deductions").getElementsByTagName("LI"); 1769 | if (num <= items.length) 1770 | { 1771 | if (items[num-1].isLegal) 1772 | items[num-1].button.onclick(); 1773 | } 1774 | } 1775 | } 1776 | -------------------------------------------------------------------------------- /docs/js/logic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // polyfill the Array includes method (which is not supported in IE). This code moved from index.html 4 | 5 | if (!Array.prototype.includes) { 6 | Object.defineProperty(Array.prototype, "includes", { 7 | enumerable: false, 8 | value: function(obj) { 9 | var newArr = this.filter(function(el) { 10 | return el == obj; 11 | }); 12 | return newArr.length > 0; 13 | } 14 | }); 15 | } 16 | 17 | 18 | 19 | /// Global variables 20 | 21 | var sentences = []; // sentences[name] is a sentence/term attached to a name string; easier to populate this dynamically than to create a general string to sentence/term parser. 22 | var unlockedLaws = []; 23 | var allLaws = []; // list of all Laws 24 | var lawsByShortName = {}; // laws indexed by shortname 25 | 26 | // convert a list of sentences, boxes, or contexts to a string 27 | function listToString(list) { 28 | if (list.length == 0) return ""; 29 | var i; 30 | var str = ""; 31 | 32 | for (i = 0; i < list.length; i++) { 33 | str += toContext(list[i]).name + ", "; 34 | } 35 | str = str.substring(0, str.length - 2); 36 | return str; 37 | } 38 | 39 | // create a deduction string 40 | function deductionString(prefix, list, conclusion) 41 | { 42 | if (list.length == 0) { 43 | if (conclusion.type == "environment") 44 | return "Form environment " + conclusion.name + "."; 45 | else 46 | return "Deduce " + conclusion.name + "."; 47 | } 48 | else { 49 | if (conclusion.type == "environment") 50 | return prefix + " " + listToString(list) + ": form environment " + conclusion.name + "."; 51 | else 52 | return prefix + " " + listToString(list) + ": deduce " + conclusion.name + "."; 53 | } 54 | } 55 | 56 | 57 | //// FreeVariable object 58 | 59 | function FreeVariable(name) 60 | { 61 | this.type = "free variable"; 62 | this.subtype = name; 63 | this.name = ""+name+""; 64 | this.longName = this.name; 65 | } 66 | 67 | // the name of a free variable associated to a non-negative integer i. Note: this is the name without the italics; should be matched to the "subtype" of a variable rather than its name. 68 | 69 | function FreeVariableName(i) { 70 | var str = "xyz"[i%3]; 71 | 72 | var j; 73 | for (j=0; j < (i-2)/3; j++) str += "'"; 74 | 75 | return str; 76 | } 77 | 78 | 79 | //// BoundVariable object. In this text, it is assumed that free and bound variables have disjoint namespaces; we'll use x,y,z,x',y', etc. for free variables and X,Y,Z,X',Y',etc. for bound variables. Note: this is the name without the italics; should be matched to the "subtype" of a variable rather than its name. 80 | 81 | function BoundVariable(name) 82 | { 83 | this.type = "bound variable"; 84 | this.subtype = name; 85 | this.name = ""+name+""; 86 | this.longName = this.name; 87 | } 88 | 89 | /// the name of a bound variable associated to a non-negative integer i 90 | 91 | function BoundVariableName(i) { 92 | var str="XYZ"[i%3]; 93 | 94 | var j; 95 | 96 | for (j=0; j < (i-2)/3; j++) str += "'"; 97 | 98 | return str; 99 | } 100 | 101 | /// Operator object. relationStyle is for 2-ary operators placed in between the arguments, e.g. x+y, as opposed to f(x,y). 102 | 103 | function Operator( name, arity, relationStyle ) 104 | { 105 | this.type = "operator"; 106 | this.subtype = name; 107 | this.name = "" + name + ""; 108 | this.arity = arity; 109 | this.relationStyle = relationStyle; 110 | 111 | switch(this.arity) 112 | { 113 | case 0: this.longName = "Constant " + this.name; break; 114 | case 1: this.longName = "Unary operator " + this.name + "()"; break; 115 | case 2: 116 | if (this.relationStyle) this.longName = "Binary operator " + this.name; 117 | else this.longName = "Binary operator " + this.name + "(,)"; 118 | } 119 | 120 | } 121 | 122 | /// Term object. The only way to make a term is to use a free or bound variable or primitive term, or to apply an operator with a list of terms. 123 | 124 | function Term() 125 | { 126 | this.type = "term"; 127 | this.subtype = ""; // will be "free variable", "bound variable", "primitive", or "operator evaluation" 128 | this.name = ""; 129 | this.longName = ""; 130 | this.operator = ""; // the operator used (only needed for operator subtype) 131 | this.argList = []; // list of args, or just the variable 132 | } 133 | 134 | // turn a free variable into a term 135 | function freeVariableTerm(free) 136 | { 137 | var term = new Term(); 138 | term.subtype = "free variable"; 139 | term.name = free.name; 140 | term.longName = free.longName; 141 | term.argList = [free]; 142 | return term; 143 | } 144 | 145 | // turn a bound variable into a term 146 | function boundVariableTerm(bound) 147 | { 148 | var term = new Term(); 149 | term.subtype = "bound variable"; 150 | term.name = bound.name; 151 | term.longName = bound.longName; 152 | term.argList = [bound]; 153 | return term; 154 | } 155 | 156 | // turn an operator and some arguments into a term 157 | function operatorTerm(operator, argList) 158 | { 159 | var term = new Term(); 160 | term.subtype = "operator evaluation"; 161 | var i; 162 | term.operator = operator; 163 | 164 | for (i=0; i < term.operator.arity; i++) 165 | term.argList[i] = toTerm(argList[i]); 166 | 167 | if (term.operator.relationStyle) { 168 | term.name = term.argList[0].longName + " " + term.operator.name + " " + term.argList[1].longName; 169 | term.longName = "(" + term.name + ")"; 170 | } else if (term.operator.arity == 0) { 171 | term.name = term.operator.name; 172 | term.longName = term.name; 173 | } else { 174 | var str = term.operator.name + "("; 175 | for (i = 0; i < term.operator.arity; i++ ) 176 | str += term.argList[i].name + ", "; 177 | str = str.substring(0, str.length - 2); 178 | term.name = str + ")"; 179 | term.longName = term.name; 180 | } 181 | return term; 182 | } 183 | 184 | function primitiveTerm(name) 185 | { 186 | var term = new Term(); 187 | term.subtype = "primitive"; 188 | term.name = name; 189 | term.longName = name; 190 | return term; 191 | } 192 | 193 | // turn an object into a term if possible 194 | function toTerm(obj) 195 | { 196 | if (typeof(obj) == "string") return primitiveTerm(obj); 197 | 198 | if (obj.type == "term") return obj; 199 | 200 | if (obj.type == "free variable") return freeVariableTerm(obj); 201 | 202 | if (obj.type == "bound variable") return boundVariableTerm(obj); 203 | 204 | error("Unrecognised type for conversion to term."); 205 | } 206 | 207 | /// Predicate object. relationStyle is for 2-ary predicates placed in between the arguments, e.g. x=y, as opposed to P(x,y). 208 | 209 | function Predicate( name, arity, relationStyle ) 210 | { 211 | this.type = "predicate"; 212 | this.subtype = name; 213 | this.name = "" + name + ""; 214 | this.arity = arity; 215 | this.relationStyle = relationStyle; 216 | 217 | switch(this.arity) 218 | { 219 | case 0: this.longName = "Proposition " + this.name; break; 220 | case 1: this.longName = "Predicate " + this.name + "()"; break; 221 | case 2: 222 | if (this.relationStyle) this.longName = "Relation " + this.name; 223 | else this.longName = "Predicate " + this.name + "(,)"; 224 | } 225 | } 226 | 227 | // we have one special predicate: the equality relation 228 | var equality = new Predicate("=", 2, true); 229 | 230 | 231 | 232 | // the sentence x=y 233 | 234 | function equals(x,y) { 235 | return predicateSentence( equality, [x,y] ); 236 | } 237 | 238 | //// Connective object 239 | 240 | function Connective(name, arity) { 241 | this.name = name; // "AND", "OR", "NOT", "IMPLIES", "IFF", "TRUE", "FALSE" 242 | this.arity = arity; // 0, 1, or 2 243 | } 244 | 245 | var ANDConnective = new Connective("AND", 2); 246 | var ORConnective = new Connective("OR", 2); 247 | var NOTConnective = new Connective("NOT", 1); 248 | var IMPLIESConnective = new Connective("IMPLIES", 2); 249 | var IFFConnective = new Connective("IFF", 2); 250 | var TRUEConnective = new Connective("TRUE", 0); 251 | var FALSEConnective = new Connective("FALSE", 0); 252 | 253 | 254 | 255 | 256 | //// Sentence object 257 | 258 | function Sentence() { 259 | this.type = ""; // "primitive", "connective", "quantifier" 260 | this.subtype = ""; // "AND", "OR", "NOT", "IMPLIES", "IFF" for connectives; "atomic" or "predicate" for primitives; "for all" or "there exists" for quantifiers 261 | this.name = ""; // name of sentence when used standalone 262 | this.longName = ""; // name of sentence when combined with other sentences 263 | this.argList = []; // the list of arguments in this sentence (usually of length 0, 1, or 2) 264 | this.predicate = ""; // the predicate used (only relevant for predicate subtype) 265 | this.connective = ""; // the connective used (only relevant for connective subtype) 266 | } 267 | 268 | // create an atomic sentence 269 | function atomicSentence(name) { 270 | var sentence = new Sentence(); 271 | sentence.type = "primitive"; 272 | sentence.subtype = "atomic"; 273 | sentence.name = ""+name+""; 274 | sentence.longName = sentence.name; 275 | return sentence; 276 | } 277 | 278 | // create a sentence from predicate and args 279 | 280 | function predicateSentence(predicate, argList) { 281 | var sentence = new Sentence; 282 | sentence.type = "primitive"; 283 | sentence.subtype = "predicate"; 284 | sentence.predicate = predicate; 285 | 286 | var i; 287 | 288 | for (i=0; i < argList.length; i++) 289 | sentence.argList[i] = toTerm(argList[i]); 290 | 291 | if (sentence.predicate.relationStyle) { 292 | sentence.name = argList[0].longName + " " + sentence.predicate.name + " " + argList[1].longName; 293 | sentence.longName = "(" + sentence.name + ")"; 294 | } else if (sentence.predicate.arity == 0) { 295 | sentence.name = sentence.predicate.name; 296 | sentence.longName = sentence.name; 297 | } else { 298 | var str = sentence.predicate.name + "("; 299 | for (i = 0; i < sentence.predicate.arity; i++ ) 300 | str += toTerm(argList[i]).name + ", "; 301 | str = str.substring(0, str.length - 2); 302 | sentence.name = str + ")"; 303 | sentence.longName = sentence.name; 304 | } 305 | 306 | sentence.name = sentence.name; 307 | return sentence; 308 | } 309 | 310 | // from a sentence from a connective and list of arguments 311 | 312 | function connectiveSentence(connective, argList) { 313 | var sentence = new Sentence(); 314 | sentence.type = "connective"; 315 | sentence.subtype = connective.name; 316 | sentence.argList = argList; 317 | sentence.connective = connective; 318 | 319 | switch(connective.arity) { 320 | case 0: 321 | sentence.name = connective.name; 322 | sentence.longName = sentence.name; 323 | break; 324 | case 1: 325 | sentence.name = connective.name + " " + argList[0].longName; 326 | sentence.longName = "(" + sentence.name + ")"; 327 | break; 328 | case 2: 329 | sentence.name = argList[0].longName + " " + connective.name + " " + argList[1].longName; 330 | sentence.longName = "(" + sentence.name + ")"; 331 | break; 332 | } 333 | return sentence; 334 | } 335 | 336 | // form the conjunction of two sentences 337 | function AND( sentence1, sentence2) { 338 | return connectiveSentence(ANDConnective, [sentence1, sentence2]); 339 | } 340 | 341 | // form the disjunction of two sentences 342 | function OR( sentence1, sentence2) { 343 | return connectiveSentence(ORConnective, [sentence1, sentence2]); 344 | } 345 | 346 | // form the implication of two sentences 347 | function IMPLIES( sentence1, sentence2) { 348 | return connectiveSentence(IMPLIESConnective, [sentence1, sentence2]); 349 | } 350 | 351 | // form the iff of two sentences 352 | function IFF( sentence1, sentence2) { 353 | return connectiveSentence(IFFConnective, [sentence1, sentence2]); 354 | } 355 | 356 | // form the negation of a sentence 357 | function NOT( sentence1) { 358 | return connectiveSentence(NOTConnective, [sentence1]); 359 | } 360 | 361 | // form the TRUE sentence 362 | function TRUE() { 363 | return connectiveSentence(TRUEConnective, []); 364 | } 365 | 366 | // form the FALSE sentence 367 | function FALSE() { 368 | return connectiveSentence(FALSEConnective, []); 369 | } 370 | 371 | // a sentence of the form "for all : " 372 | function forAll(predicate, bound) { 373 | var sentence = new Sentence(); 374 | sentence.type = "quantifier"; 375 | sentence.subtype = "for all"; 376 | sentence.name = "FOR ALL " + bound.name + ": " + predicate.longName; 377 | sentence.longName = "(" + sentence.name + ")"; 378 | sentence.argList = [predicate, toTerm(bound)]; 379 | sentence.name = sentence.name; 380 | return sentence; 381 | } 382 | 383 | // a sentence of the form "there exists : " 384 | function thereExists(predicate, bound) { 385 | var sentence = new Sentence(); 386 | sentence.type = "quantifier"; 387 | sentence.subtype = "there exists"; 388 | sentence.name = "THERE EXISTS " + bound.name + ": " + predicate.longName; 389 | sentence.longName = "(" + sentence.name + ")"; 390 | sentence.argList = [predicate, toTerm(bound)]; 391 | sentence.name = sentence.name; 392 | return sentence; 393 | } 394 | 395 | // tries to convert an obj to sentence if it can 396 | 397 | function toSentence(obj) { 398 | if (typeof(obj) == 'string') return atomicSentence(obj); 399 | if (obj instanceof Sentence ) return obj; 400 | if (obj instanceof Context ) return obj.sentence; 401 | if (obj instanceof Law) return obj.conclusion.sentence; 402 | if (obj instanceof Exercise) return obj.law.conclusion.sentence; 403 | if (obj instanceof Assumption) return obj.sentence; 404 | if (obj.type == "sentenceBox") return obj.sentence; 405 | if (obj.type == "formulaBox") return obj.sentence; 406 | error("Unable to convert object to sentence."); 407 | } 408 | 409 | 410 | //some atomic sentences. Moved from index.html 411 | 412 | var A = atomicSentence("A"); 413 | var B = atomicSentence("B"); 414 | var C = atomicSentence("C"); 415 | var D = atomicSentence("D"); 416 | 417 | // Law object 418 | 419 | function Law(shortName, name, givens, conclusion) { 420 | this.shortName = shortName; 421 | this.name = name; // name of law, e.g. "EXERCISE 1.1" 422 | 423 | 424 | // givens is an array of given hypotheses (can be empty). I allow sentences as givens, so these need to be converted to contexts. 425 | var givenslist = []; 426 | givens.forEach( function(given) { 427 | givenslist.push(toContext(given)); 428 | }); 429 | this.givens = givenslist; 430 | this.conclusion = toContext(conclusion); // given conclusion 431 | this.unlocked = false; // by default the law is not unlocked 432 | this.string = deductionString("Given", givens, this.conclusion); 433 | this.index = allLaws.length; // the order of the law in the text (used to determine circularity) - the allLaws.length is a placeholder, will be overwritten 434 | this.clone = ""; // points to the clone of the law with additional root environment, if needed 435 | 436 | // for most laws, the matching givens and conclusions template is the same as what is displayed to the user. 437 | this.givensTemplate = this.givens; 438 | this.conclusionTemplate = this.conclusion; 439 | 440 | allLaws.push(this); 441 | lawsByShortName[shortName] = this; 442 | 443 | if (allFormulas(this.givens)) { 444 | if (this.conclusion.type == 'sentence in environment' || this.conclusion.type == 'environment') { 445 | var givensClone = this.givens.slice(0); 446 | givensClone.push(rootEnvironmentContext()); 447 | this.clone = new Law(this.shortName + "Clone", this.name, givensClone, this.conclusion); 448 | } 449 | } 450 | } 451 | 452 | 453 | 454 | 455 | // add (name of) expr to list if not already there 456 | 457 | function record(list, expr) { 458 | if (!list.includes(expr.name)) { 459 | list.push(expr.name); 460 | sentences[expr.name] = expr; // remember the variable object associated to name 461 | } 462 | } 463 | 464 | function makeOptions(getPrimitives, getFreeVars, getBoundVars, getPrimTerms, getPredicates, getOperators, getAtomic) { 465 | var options = []; 466 | options.getPrimitives = getPrimitives; 467 | options.getFreeVars = getFreeVars; 468 | options.getBoundVars = getBoundVars; 469 | options.getPrimTerms = getPrimTerms; 470 | options.getPredicates = getPredicates; 471 | options.getOperators = getOperators; 472 | options.getAtomic = getAtomic; 473 | return options; 474 | } 475 | 476 | // list the (names of) atomic primitives, free variables, bound variables, primitive terms, predicates, operators occurring in a law 477 | 478 | function listPrimitives(law, getPrimitives, getFreeVars, getBoundVars, getPrimTerms, getPredicates, getOperators, getAtomic, useTemplate) { 479 | var list = []; 480 | var options = makeOptions(getPrimitives, getFreeVars, getBoundVars, getPrimTerms, getPredicates, getOperators, getAtomic); 481 | 482 | var givens; 483 | var conclusion; 484 | 485 | if (useTemplate) { 486 | givens = law.givensTemplate; 487 | conclusion = law.conclusionTemplate; 488 | } else { 489 | givens = law.givens; 490 | conclusion = law.conclusion; 491 | } 492 | 493 | givens.forEach( function(item) { 494 | pushPrimitivesFromContext(list, toContext(item), options); 495 | }); 496 | 497 | // usually the line below is redundant, as any primitives in conclusion should have already appeared in one of the givens, but there are some exceptions, e.g. universal introduction without specifying the bound variable 498 | pushPrimitivesFromContext(list, conclusion, options); 499 | 500 | return list; 501 | } 502 | 503 | // push all the primitives from context onto list (removing duplicates) 504 | function pushPrimitivesFromContext(list, context, options) { 505 | 506 | if (context.type == "formula" || context.type == "sentence in environment") { 507 | pushPrimitivesFromSentence(list, context.sentence, options); 508 | } 509 | if (context.type == "environment" || context.type == "sentence in environment") 510 | { 511 | context.environment.forEach( function(item) { 512 | if (item.type == "assuming" || item.type == "setting") { 513 | pushPrimitivesFromSentence(list, item.sentence, options); 514 | } 515 | if (item.type == "setting" || item.type == "letting") { 516 | if (options.getFreeVars) { 517 | record(list, item.variable); 518 | } 519 | } 520 | }); 521 | } 522 | if (context.type == "term context") { 523 | pushPrimitivesFromSentence(list, context.term, options); 524 | } 525 | } 526 | 527 | // push all the primitives from sentence/term onto list (removing duplicates) 528 | function pushPrimitivesFromSentence(list, sentence, options) 529 | { 530 | 531 | switch(sentence.type) { 532 | case "primitive": 533 | if (options.getPrimitives) record(list, sentence); 534 | 535 | switch(sentence.subtype) { 536 | case "atomic": 537 | if (options.getAtomic) record(list, sentence); 538 | break; 539 | case "predicate": 540 | if (options.getPredicates) record(list, sentence.predicate); 541 | sentence.argList.forEach( function(arg) { pushPrimitivesFromSentence(list,arg, options);} ); 542 | break; 543 | } 544 | break; 545 | case "quantifier": 546 | if (options.getBoundVars) record(list, sentence.argList[1]); 547 | pushPrimitivesFromSentence(list, sentence.argList[0], options); 548 | break; 549 | case "connective": 550 | sentence.argList.forEach( function(arg) { pushPrimitivesFromSentence(list,arg, options);} ); 551 | break; 552 | case "term": 553 | switch(sentence.subtype) { 554 | case "primitive": 555 | if (options.getPrimTerms) record(list,sentence); 556 | return; 557 | case "operator evaluation": 558 | if (options.getOperators) record(list, sentence.operator); 559 | sentence.argList.forEach( function(arg) { pushPrimitivesFromSentence(list,arg, options);} ); 560 | return; 561 | case "free variable": 562 | if (options.getFreeVars) record(list, sentence.argList[0]); 563 | return; 564 | case "bound variable": 565 | if (options.getBoundVars) record(list, sentence.argList[0]); 566 | return; 567 | } 568 | } 569 | } 570 | 571 | 572 | 573 | // returns true if all terms in givens are formulas or terms (i.e., no environment is involved) 574 | 575 | function allFormulas(givens) { 576 | var i; 577 | for (i = 0; i < givens.length; i++) { 578 | if (givens[i].type != "formula" && givens[i].type != "term context") return false; 579 | } 580 | return true; 581 | } 582 | 583 | // a tricky routine: tries to match arglist to the givens of a law and see what the primitives are, returning this data in an output object. Note: for predicate logic, some of the matches need to be discarded because the conclusion does not obey scoping laws (e.g. repeated free variables, etc.). Also there may be situations where there are multiple possible substitutions (creating existential quantifier) 584 | 585 | function matchWithGivens( arglist, law, primitives ) { 586 | 587 | // for matching, we use the template, rather than the givens and conclusion displayed to user (but these are usually the same) 588 | var givens = law.givensTemplate; 589 | var conclusion = law.conclusionTemplate; 590 | 591 | var output = new Object(); 592 | output.matches = true; // so far, no reason to doubt a match. 593 | output.illegal = false; // sometimes there is technically a match but something is ill-formed 594 | output.multivalued = false; // do we need multiple conclusions, or just one? 595 | output.env = []; // by default, the output environment will be the root one. 596 | 597 | primitives.forEach( function(primitive) { 598 | output[primitive] = ""; 599 | }); 600 | 601 | 602 | // technically one needs to ensure that primitives and free variables avoid reserved words such as "matches". This is unlikely to come up in practice. 603 | 604 | if (arglist.length != givens.length) { 605 | output.matches = false; 606 | return output; 607 | } 608 | 609 | // convert everything to contexts if not already done so (this step may be redundant) 610 | 611 | var i; 612 | for (i = 0; i < givens.length; i++) { 613 | arglist[i] = toContext(arglist[i]); 614 | givens[i] = toContext(givens[i]); 615 | } 616 | 617 | // check if all the givens are formulas 618 | 619 | if (!allFormulas(givens)) 620 | { 621 | var proposedYet = false; 622 | var proposedEnv = []; 623 | 624 | 625 | for (i = 0; i < givens.length; i++) { 626 | if (givens[i].type == "sentence in environment" || givens[i].type == "environment") { 627 | if (arglist[i].environment.length < givens[i].environment.length) { 628 | // can't match if the template has more deeply nested assumptions than the arglist! 629 | output.matches = false; 630 | return output; 631 | } 632 | var candidateEnv = arglist[i].environment.slice( 0, arglist[i].environment.length - givens[i].environment.length); 633 | if (proposedYet == false) { 634 | proposedYet = true; 635 | proposedEnv = candidateEnv; 636 | } 637 | else if (assumptionListToString(proposedEnv) != assumptionListToString(candidateEnv)) { // need to convert to string here as a proxy for passing by value rather than by reference 638 | output.matches = false; 639 | return output; 640 | } 641 | } 642 | } 643 | output.env = proposedEnv; 644 | } 645 | 646 | switch(law.shortName) { // a number of laws are too complex to be matched by the standard algorithm and have to be treated separately 647 | case "UniversalIntroduction": 648 | case "UniversalIntroduction2": 649 | matchUniversalIntroduction(arglist, output, law); 650 | break; 651 | case "UniversalSpecification": 652 | case "UniversalSpecification2": 653 | matchUniversalSpecification(arglist, output, law); 654 | break; 655 | case "ExistentialInstantiation": 656 | case "ExistentialInstantiation2": 657 | matchExistentialInstantiation(arglist, output, law); 658 | break; 659 | case "ExistentialIntroduction": 660 | case "ExistentialIntroduction2": 661 | matchExistentialIntroduction(arglist, output, law); 662 | break; 663 | case "Indiscernability": 664 | matchIndiscernability(arglist, output, law); 665 | break; 666 | case "UniversalRenamingBoundVar": 667 | case "ExistentialRenamingBoundVar": 668 | matchRenamingBoundVar(arglist, output, law); 669 | break; 670 | case "BarbaraSingular": 671 | matchBarbaraSingular(arglist, output); 672 | break; 673 | default: 674 | var i; 675 | 676 | for (i = 0; i < givens.length; i++) { 677 | matchWithGiven( arglist[i], givens[i], output); 678 | if (!output.matches) return output; 679 | } 680 | 681 | for (i=0; i < primitives.length; i++) { 682 | if (output[primitives[i]] == "") { // somehow failed to match one of the primitives 683 | output.matches = false; 684 | return output; 685 | } 686 | } 687 | output.conclusion = subs(conclusion, output); 688 | } 689 | 690 | return output; 691 | } 692 | 693 | // match arglist against one of the two laws of universal introduction and report the conclusions in output 694 | function matchUniversalIntroduction(arglist, output, law) { 695 | if (!output.matches) return; 696 | 697 | // arglist[0] needs to be of the form "A, [letting x be arbitrary]" after the output.env 698 | if (arglist[0].type != "sentence in environment") { 699 | output.matches = false; 700 | return; 701 | } 702 | 703 | var assumption = arglist[0].environment[output.env.length]; 704 | 705 | if (assumption.type != "letting") { 706 | output.matches = false; 707 | return; 708 | } 709 | 710 | var freeVariable = assumption.variable; 711 | var statement = arglist[0].sentence; 712 | 713 | var boundVariable; 714 | 715 | switch (law.shortName) { 716 | case "UniversalIntroduction": 717 | if (arglist[1].type != "term context") { 718 | output.matches = false; 719 | return; 720 | } 721 | if (arglist[1].term.subtype != "bound variable") { 722 | output.matches = false; 723 | return; 724 | } 725 | boundVariable = arglist[1].term.argList[0]; 726 | break; 727 | case "UniversalIntroduction2": 728 | if (arglist[1].type != "environment") { 729 | output.matches = false; 730 | return; 731 | } 732 | // choose the next available bound Variable 733 | boundVariable = nextAvailableBoundVariable(statement); 734 | break; 735 | } 736 | 737 | 738 | var newSentence = forAll( searchReplace( statement, freeVariable, boundVariable), boundVariable); 739 | output.conclusion = sentenceContext( newSentence, output.env ); 740 | } 741 | 742 | 743 | // return the next availalbe bound variable not already in a statement 744 | 745 | function nextAvailableBoundVariable(statement) { 746 | var boundVars = []; 747 | 748 | 749 | pushPrimitivesFromSentence(boundVars, statement, makeOptions(false, false, true,false, false, false, false)); 750 | 751 | 752 | var num=0; 753 | var match; 754 | var str, longStr; 755 | 756 | do { 757 | str = BoundVariableName(num); 758 | longStr = ""+str+""; 759 | match = boundVars.includes(longStr); 760 | num++; 761 | } while (match); 762 | return new BoundVariable(str); 763 | 764 | } 765 | 766 | 767 | // match arglist against the law of universal specification and report the conclusions in output 768 | function matchUniversalSpecification(arglist, output, law) { 769 | if (!output.matches) return; 770 | 771 | 772 | // arglist[0] needs to be of the form "FOR ALL X: P(X)" after the output.env 773 | if (arglist[0].type != "sentence in environment") { 774 | output.matches = false; 775 | return; 776 | } 777 | 778 | if (arglist[0].sentence.type != "quantifier" || arglist[0].sentence.subtype != "for all") { 779 | output.matches = false; 780 | return; 781 | } 782 | 783 | var sentence = arglist[0].sentence.argList[0]; 784 | var boundVar = toTerm(arglist[0].sentence.argList[1]).argList[0]; 785 | 786 | if (arglist[1].type != "term context") { 787 | output.matches = false; 788 | return; 789 | } 790 | 791 | var term = toTerm(arglist[1].term); 792 | var newSentence = searchReplace( sentence, boundVar, term ); 793 | 794 | if (law.shortName == "UniversalSpecification") { 795 | if (hasBoundOrUnknownFree(term, output.env)) { 796 | output.illegal = true; // will keep matching but will display in silver 797 | } 798 | output.conclusion = sentenceContext( newSentence, output.env ); 799 | } else if (law.shortName == "UniversalSpecification2") { 800 | if (term.subtype != "free variable") { 801 | output.matches = false; 802 | return; 803 | } 804 | var env = output.env.slice(0); 805 | env.push(toAssumption(term.argList[0])); 806 | output.conclusion = sentenceContext( newSentence, env ); 807 | } 808 | 809 | } 810 | 811 | // returns true if term contains a bound variable or a free variable not already in environment 812 | 813 | function hasBoundOrUnknownFree(term,env) { 814 | switch(term.subtype) { 815 | case "primitive": 816 | return false; 817 | case "free variable": 818 | var i; 819 | for (i=0; i < env.length; i++) 820 | if (env[i].type == 'letting' || env[i].type == 'setting') 821 | if (env[i].variable.name == term.argList[0].name) 822 | return false; 823 | return true; 824 | case "bound variable": 825 | return true; 826 | case "operator": 827 | var i; 828 | for (i=0; i < term.argList.length; i++) 829 | if (hasBoundOrUnknownFree(term.argList[i],env)) return true; 830 | return false; 831 | } 832 | } 833 | 834 | // replace all occurrences of term "search" with term "replace" 835 | 836 | function searchReplace(statement, search, replace) { 837 | var newArgList = []; 838 | var i; 839 | 840 | 841 | if (statement.type == "free variable" || statement.type == "bound variable") return statement; 842 | 843 | if (statement.name == search.name) return toTerm(replace); 844 | 845 | for (i=0; i < statement.argList.length; i++) 846 | newArgList[i] = searchReplace(statement.argList[i], search, replace); 847 | 848 | 849 | switch (statement.type) { 850 | case "term": 851 | if (statement.subtype == "free variable") return statement; 852 | if (statement.subtype == "bound variable") return statement; 853 | if (statement.subtype == "primitive") return statement; 854 | if (statement.subtype == "operator evaluation") return operatorTerm(statement.operator, newArgList); 855 | return; 856 | case "primitive": 857 | if (statement.subtype == "atomic") return statement; 858 | if (statement.subtype == "predicate") return predicateSentence(statement.predicate, newArgList); 859 | return; 860 | case "connective": 861 | return connectiveSentence(statement.connective, newArgList); 862 | case "quantifier": 863 | if (statement.subtype == "for all") return forAll( newArgList[0], newArgList[1]); 864 | if (statement.subtype == "there exists") return thereExists( newArgList[0], newArgList[1]); 865 | return; 866 | } 867 | } 868 | 869 | 870 | // match arglist against the law of existential instantiation and report the conclusions in output 871 | function matchExistentialInstantiation(arglist, output, law) { 872 | if (!output.matches) return; 873 | 874 | // arglist[0] needs to be of the form "THERE EXISTS X: P(X)" after the output.env 875 | if (arglist[0].type != "sentence in environment") { 876 | output.matches = false; 877 | return; 878 | } 879 | 880 | if (arglist[0].sentence.type != "quantifier" || arglist[0].sentence.subtype != "there exists") { 881 | output.matches = false; 882 | return; 883 | } 884 | 885 | var sentence = arglist[0].sentence.argList[0]; 886 | var boundVar = toTerm(arglist[0].sentence.argList[1]).argList[0]; 887 | 888 | var freeVariable; 889 | 890 | if (law.shortName == "ExistentialInstantiation") { 891 | if (arglist[1].type != "term context") { 892 | output.matches = false; 893 | return; 894 | } 895 | if (arglist[1].term.subtype != "free variable") { 896 | output.matches = false; 897 | return; 898 | } 899 | freeVariable = arglist[1].term.argList[0]; 900 | } else if (law.shortName == "ExistentialInstantiation2") { 901 | // choose the next available free Variable 902 | var freeVars = []; 903 | 904 | pushPrimitivesFromContext(freeVars, sentenceContext(sentence,output.env), makeOptions(false, true, false, false, false, false, false)); 905 | 906 | var num=0; 907 | var match; 908 | var str, longStr; 909 | 910 | do { 911 | str = FreeVariableName(num); 912 | longStr = ""+str+""; 913 | match = freeVars.includes(longStr); 914 | num++; 915 | } while (match); 916 | 917 | freeVariable = new FreeVariable(str); 918 | } 919 | 920 | var newSentence = searchReplace( sentence, boundVar, freeVariable ); 921 | var env = output.env.slice(0); 922 | env.push( settingAssumption(newSentence, freeVariable) ); 923 | output.conclusion = sentenceContext( newSentence, env ); 924 | } 925 | 926 | // match arglist against one of the two laws of existential introduction and report the conclusions in output 927 | function matchExistentialIntroduction(arglist, output, law) { 928 | if (!output.matches) return; 929 | 930 | 931 | // arglist[0] needs to be of the form "P(X)" after the output.env 932 | if (arglist[0].type != "sentence in environment") { 933 | output.matches = false; 934 | return; 935 | } 936 | 937 | var sentence = arglist[0].sentence; 938 | 939 | // arglist[1] needs to be a term context 940 | 941 | if (arglist[1].type != "term context") { 942 | output.matches = false; 943 | return; 944 | } 945 | 946 | var term = arglist[1].term; 947 | 948 | if (hasBoundOrUnknownFree(term, output.env)) { 949 | output.illegal = true; // will keep matching but will display in silver 950 | } 951 | 952 | var boundVariable; 953 | 954 | switch (law.shortName) { 955 | case "ExistentialIntroduction2": 956 | if (arglist[2].type != "term context") { 957 | output.matches = false; 958 | return; 959 | } 960 | if (arglist[2].term.subtype != "bound variable") { 961 | output.matches = false; 962 | return; 963 | } 964 | boundVariable = arglist[2].term.argList[0]; 965 | break; 966 | case "ExistentialIntroduction": 967 | // choose the next available bound Variable 968 | boundVariable = nextAvailableBoundVariable(sentence); 969 | break; 970 | } 971 | 972 | output.multivalued = true; 973 | 974 | var translations = allSearchReplace(sentence, term, boundVariable); 975 | 976 | output.conclusions = []; 977 | var i; 978 | for (i=0; i < translations.length; i++) 979 | output.conclusions.push(sentenceContext( thereExists(translations[i],boundVariable), output.env )); 980 | } 981 | 982 | 983 | // match arglist against the law of indiscernability and report the conclusions in output 984 | function matchIndiscernability(arglist, output, law) { 985 | if (!output.matches) return; 986 | 987 | // arglist[0], arglist[1] have to be sentences 988 | if (arglist[0].type != "sentence in environment" || arglist[1].type != "sentence in environment") { 989 | output.matches = false; 990 | return; 991 | } 992 | 993 | var sentence = arglist[0].sentence; 994 | var sentence2 = arglist[1].sentence; 995 | 996 | // sentence2 needs to be of the form alpha=beta 997 | 998 | if (sentence2.subtype != "predicate" || sentence2.predicate != equality) { 999 | output.matches = false; 1000 | return; 1001 | } 1002 | 1003 | var alpha = sentence2.argList[0]; 1004 | var beta = sentence2.argList[1]; 1005 | 1006 | output.multivalued = true; 1007 | 1008 | var translations = allSearchReplace(sentence, alpha, beta); 1009 | 1010 | output.conclusions = []; 1011 | var i; 1012 | for (i=0; i < translations.length; i++) 1013 | if (translations[i].name != sentence.name) //remove trivial applications of indiscernability in which ibe deduces a sentence from itself 1014 | output.conclusions.push(sentenceContext( translations[i], output.env )); 1015 | } 1016 | 1017 | // match arglist against the law of existential instantiation and report the conclusions in output 1018 | function matchExistentialInstantiation(arglist, output, law) { 1019 | if (!output.matches) return; 1020 | 1021 | // arglist[0] needs to be of the form "THERE EXISTS X: P(X)" after the output.env 1022 | if (arglist[0].type != "sentence in environment") { 1023 | output.matches = false; 1024 | return; 1025 | } 1026 | 1027 | if (arglist[0].sentence.type != "quantifier" || arglist[0].sentence.subtype != "there exists") { 1028 | output.matches = false; 1029 | return; 1030 | } 1031 | 1032 | var sentence = arglist[0].sentence.argList[0]; 1033 | var boundVar = toTerm(arglist[0].sentence.argList[1]).argList[0]; 1034 | 1035 | var freeVariable; 1036 | 1037 | if (law.shortName == "ExistentialInstantiation") { 1038 | if (arglist[1].type != "term context") { 1039 | output.matches = false; 1040 | return; 1041 | } 1042 | if (arglist[1].term.subtype != "free variable") { 1043 | output.matches = false; 1044 | return; 1045 | } 1046 | freeVariable = arglist[1].term.argList[0]; 1047 | } else if (law.shortName == "ExistentialInstantiation2") { 1048 | // choose the next available free Variable 1049 | var freeVars = []; 1050 | 1051 | pushPrimitivesFromContext(freeVars, sentenceContext(sentence,output.env), makeOptions(false, true, false, false, false, false, false)); 1052 | 1053 | var num=0; 1054 | var match; 1055 | var str, longStr; 1056 | 1057 | do { 1058 | str = FreeVariableName(num); 1059 | longStr = ""+str+""; 1060 | match = freeVars.includes(longStr); 1061 | num++; 1062 | } while (match); 1063 | 1064 | freeVariable = new FreeVariable(str); 1065 | } 1066 | 1067 | var newSentence = searchReplace( sentence, boundVar, freeVariable ); 1068 | var env = output.env.slice(0); 1069 | env.push( settingAssumption(newSentence, freeVariable) ); 1070 | output.conclusion = sentenceContext( newSentence, env ); 1071 | } 1072 | 1073 | // match arglist against the law of universal or existential renaming of bound variables and report the conclusions in output 1074 | function matchRenamingBoundVar(arglist, output,law) { 1075 | if (!output.matches) return; 1076 | 1077 | // arglist[0] needs to be of the form "FOR ALL X: P(X)" or "THERE EXISTS X: P(X)" after the output.env 1078 | if (arglist[0].type != "sentence in environment") { 1079 | output.matches = false; 1080 | return; 1081 | } 1082 | 1083 | var subtype; 1084 | if (law.shortName == "UniversalRenamingBoundVar") subtype = "for all"; 1085 | if (law.shortName == "ExistentialRenamingBoundVar") subtype = "there exists"; 1086 | 1087 | if (arglist[0].sentence.type != "quantifier" || arglist[0].sentence.subtype != subtype) { 1088 | output.matches = false; 1089 | return; 1090 | } 1091 | 1092 | var sentence = arglist[0].sentence.argList[0]; 1093 | var boundVar = arglist[0].sentence.argList[1]; 1094 | 1095 | // arglist[1] needs to be a bound variable 1096 | 1097 | if (arglist[1].type != "term context") { 1098 | output.matches = false; 1099 | return; 1100 | } 1101 | 1102 | if (arglist[1].term.subtype != "bound variable") { 1103 | output.matches = false; 1104 | return; 1105 | } 1106 | 1107 | var newBoundVar = arglist[1].term.argList[0]; 1108 | 1109 | // it can happen that newBoundVar already appears in sentence; this makes the conclusion illegal, but we will still list the deduction as being greyed out 1110 | 1111 | 1112 | var newSentence = searchReplace(sentence, boundVar, newBoundVar); 1113 | 1114 | if (law.shortName == "UniversalRenamingBoundVar") 1115 | output.conclusion = sentenceContext( forAll( newSentence, newBoundVar), output.env); 1116 | 1117 | if (law.shortName == "ExistentialRenamingBoundVar") 1118 | output.conclusion = sentenceContext( thereExists( newSentence, newBoundVar), output.env); 1119 | 1120 | } 1121 | 1122 | 1123 | // match arglist against the singular form of the Barbara syllogism and report the conclusions in output 1124 | function matchBarbaraSingular(arglist, output) { 1125 | if (!output.matches) return; 1126 | 1127 | // arglist[0] needs to be of the form "FOR ALL X: P(X) IMPLIES Q(X)" after the output.env 1128 | if (arglist[0].type != "sentence in environment") { 1129 | output.matches = false; 1130 | return; 1131 | } 1132 | if (arglist[0].sentence.type != "quantifier" || arglist[0].sentence.subtype != "for all") { 1133 | output.matches = false; 1134 | return; 1135 | } 1136 | 1137 | var sentence = arglist[0].sentence.argList[0]; 1138 | var X = arglist[0].sentence.argList[1]; 1139 | 1140 | if (sentence.type != "connective" || sentence.subtype != "IMPLIES") { 1141 | output.matches = false; 1142 | return; 1143 | } 1144 | 1145 | var PX = sentence.argList[0]; 1146 | var QX = sentence.argList[1]; 1147 | 1148 | // arglist[1] needs to be a sentence 1149 | 1150 | if (arglist[1].type != "sentence in environment") { 1151 | output.matches = false; 1152 | return; 1153 | } 1154 | 1155 | var Pa = arglist[1].sentence; 1156 | 1157 | // arglist[2] needs to be a term 1158 | 1159 | if (arglist[2].type != "term context") { 1160 | output.matches = false; 1161 | return; 1162 | } 1163 | 1164 | var alpha = arglist[2].term; 1165 | 1166 | // as with universalSpecification, alpha cannot contain bound variables or free variables not present in the environment. 1167 | 1168 | if (hasBoundOrUnknownFree(alpha, output.env)) { 1169 | output.illegal = true; // will keep matching but will display in silver 1170 | } 1171 | 1172 | // Pa has to match what happens to PX when X is replaced by alpha 1173 | 1174 | var testSentence = searchReplace(PX, X, alpha); 1175 | 1176 | if (testSentence.name != Pa.name) { 1177 | output.matches = false; 1178 | return; 1179 | } 1180 | 1181 | output.conclusion = sentenceContext( searchReplace(QX, X, alpha), output.env); 1182 | } 1183 | 1184 | 1185 | // return a list of all possible ways in which appearances of "search" in sentence can be replaced with "replace" 1186 | 1187 | function allSearchReplace(statement, search, replace) 1188 | { 1189 | if (statement.type == "free variable" || statement.type == "bound variable") return [statement]; 1190 | 1191 | var list = []; 1192 | var newArgList = []; 1193 | var i,j; 1194 | 1195 | if (statement.name == search.name) list.push(toTerm(replace)); 1196 | 1197 | for (i=0; i < statement.argList.length; i++) 1198 | newArgList[i] = allSearchReplace(statement.argList[i], search, replace); 1199 | 1200 | switch (statement.type) { 1201 | case "term": 1202 | switch (statement.subtype) { 1203 | case "free variable": 1204 | case "bound variable": 1205 | case "primitive": 1206 | list.push(statement); 1207 | break; 1208 | case "operator evaluation": 1209 | switch(statement.operator.arity) { 1210 | case 0: 1211 | list = [operatorTerm(statement.operator, [])]; 1212 | break; 1213 | case 1: 1214 | for (i=0; i < newArgList[0].length; i++) 1215 | list.push( operatorTerm(statement.operator, [newArgList[0][i]])); 1216 | break; 1217 | case 2: 1218 | for (i=0; i < newArgList[0].length; i++) 1219 | for (j=0; j < newArgList[1].length; j++) 1220 | list.push( operatorTerm( statement.operator, [newArgList[0][i], newArgList[1][j]])); 1221 | break; 1222 | } 1223 | break; 1224 | } 1225 | break; 1226 | case "primitive": 1227 | switch(statement.subtype) { 1228 | case "atomic": 1229 | list.push(statement); 1230 | break; 1231 | case "predicate": 1232 | switch(statement.predicate.arity) { 1233 | case 0: 1234 | list.push(predicateSentence(statement.predicate, [])); 1235 | break; 1236 | case 1: 1237 | for (i=0; i < newArgList[0].length; i++) 1238 | list.push( predicateSentence(statement.predicate, [newArgList[0][i]])); 1239 | break; 1240 | case 2: 1241 | for (i=0; i < newArgList[0].length; i++) 1242 | for (j=0; j < newArgList[1].length; j++) 1243 | list.push( predicateSentence( statement.predicate, [newArgList[0][i], newArgList[1][j]])); 1244 | break; 1245 | } 1246 | break; 1247 | } 1248 | break; 1249 | case "connective": 1250 | switch(statement.connective.arity) { 1251 | case 0: 1252 | list.push(connectiveSentence(statement.connective, [])); 1253 | break; 1254 | case 1: 1255 | for (i=0; i < newArgList[0].length; i++) 1256 | list.push( connectiveSentence(statement.connective, [newArgList[0][i]])); 1257 | break; 1258 | case 2: 1259 | for (i=0; i < newArgList[0].length; i++) 1260 | for (j=0; j < newArgList[1].length; j++) 1261 | list.push( connectiveSentence( statement.connective, [newArgList[0][i], newArgList[1][j]])); 1262 | break; 1263 | } 1264 | break; 1265 | case "quantifier": 1266 | switch(statement.subtype) { 1267 | case "for all": 1268 | for (i=0; i < newArgList[0].length; i++) 1269 | for (j=0; j < newArgList[1].length; j++) 1270 | list.push( forAll( newArgList[0][i], newArgList[1][j])); 1271 | break; 1272 | case "there exists": 1273 | for (i=0; i < newArgList[0].length; i++) 1274 | for (j=0; j < newArgList[1].length; j++) 1275 | list.push( thereExists( newArgList[0][i], newArgList[1][j])); 1276 | break; 1277 | } 1278 | break; 1279 | } 1280 | return list; 1281 | } 1282 | 1283 | 1284 | // try to match a context with a template context and store the match in output 1285 | 1286 | function matchWithGiven( context, template, output) { 1287 | if (!output.matches) return; 1288 | 1289 | if (context.type != template.type) { 1290 | output.matches = false; 1291 | return; 1292 | } 1293 | 1294 | if (template.type == "environment" || template.type == "sentence in environment") { 1295 | var i; 1296 | for (i = 0; i < template.environment.length; i++) { 1297 | matchWithGivenAssumption( context.environment[i + output.env.length], template.environment[i], output); 1298 | if (!output.matches) return; 1299 | } 1300 | } 1301 | 1302 | if (template.type == "formula" || template.type == "sentence in environment") 1303 | matchWithGivenSentence( context.sentence, template.sentence, output); 1304 | 1305 | if (template.type == "term context") 1306 | matchWithGivenSentence( context.term, template.term, output); 1307 | } 1308 | 1309 | // match item to template object in output if this has not already been done 1310 | function makeMatch(item, template, output) { 1311 | if (item.name == output[template.name].name) { 1312 | // good, it matches what we've already fit to the template! 1313 | return; 1314 | } 1315 | if (output[template.name] == "") { 1316 | output[template.name] = item; 1317 | return; 1318 | } 1319 | output.matches = false; 1320 | return; 1321 | 1322 | } 1323 | 1324 | // try to match an assumption with a template sentence and store the match in output 1325 | 1326 | function matchWithGivenAssumption( assumption, template, output) { 1327 | if (!output.matches) return; 1328 | 1329 | if (assumption.type != template.type) { 1330 | output.matches = false; 1331 | return; 1332 | } 1333 | 1334 | 1335 | if (assumption.type == 'assuming' || assumption.type == 'setting') { 1336 | matchWithGivenSentence( assumption.sentence, template.sentence, output); 1337 | } 1338 | 1339 | if (assumption.type == 'letting' || assumption.type == 'setting') { 1340 | makeMatch(assumption.variable, template.variable, output); 1341 | } 1342 | 1343 | } 1344 | 1345 | // try to match a sentence or term with a template and store the match in output 1346 | 1347 | function matchWithGivenSentence( sentence, template, output) { 1348 | if (!output.matches) return; 1349 | 1350 | switch (template.type) { 1351 | case "primitive": 1352 | switch (template.subtype) { 1353 | case "atomic": 1354 | makeMatch(sentence,template,output); 1355 | return; 1356 | case "predicate": 1357 | if (template.predicate == equality) { 1358 | if (sentence.predicate != equality) { 1359 | output.matches = false; 1360 | return; 1361 | } 1362 | var i; 1363 | 1364 | for (i=0; i