├── .eslintrc.js
├── .github
└── workflows
│ ├── build.yml
│ ├── deploy.yml
│ ├── lint.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── jest.config.js
├── package.json
├── src
├── App.vue
├── components
│ ├── AboutSection.vue
│ ├── InputTips.vue
│ ├── MainPage.vue
│ ├── NormalForms.vue
│ ├── SimplificationButton.vue
│ ├── SimplificationSteps.vue
│ └── WangHaoProof.vue
├── core
│ ├── AstNode
│ │ ├── AndNode.ts
│ │ ├── AtomNode.ts
│ │ ├── EqNode.ts
│ │ ├── FalseNode.ts
│ │ ├── ImpliedByNode.ts
│ │ ├── ImplyNode.ts
│ │ ├── NandNode.ts
│ │ ├── NorNode.ts
│ │ ├── NotNode.ts
│ │ ├── OrNode.ts
│ │ ├── TrueNode.ts
│ │ ├── XorNode.ts
│ │ ├── base.ts
│ │ ├── index.ts
│ │ └── precedence.ts
│ ├── QuineMcCluskey
│ │ ├── Minterm.ts
│ │ ├── QuineMcCluskey.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ ├── WangHao.ts
│ ├── buildAst.ts
│ ├── equivalents.ts
│ ├── getTable.ts
│ ├── transformExp.spec.ts
│ └── transformExp.ts
├── main.ts
├── shims-vue.d.ts
└── types
│ ├── WangHaoTooLongError.ts
│ ├── data.ts
│ ├── equivalent.ts
│ └── step.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | browser: true,
6 | es2015: true,
7 | jest: true,
8 | },
9 | extends: [
10 | '@vue/typescript/recommended',
11 | 'plugin:vue/vue3-recommended',
12 | 'eslint:recommended',
13 | 'airbnb-base',
14 | ],
15 | parserOptions: {
16 | ecmaVersion: 'latest',
17 | sourceType: 'module',
18 | },
19 | globals: {
20 | defineProps: 'readonly',
21 | defineEmits: 'readonly',
22 | defineExpose: 'readonly',
23 | withDefaults: 'readonly',
24 | },
25 | rules: {
26 | 'import/extensions': 0,
27 | 'import/no-unresolved': 0,
28 | 'import/no-extraneous-dependencies': [
29 | 'error',
30 | {
31 | devDependencies: [
32 | './*.ts',
33 | ],
34 | },
35 | ],
36 |
37 | semi: 0,
38 | '@typescript-eslint/semi': 2,
39 |
40 | '@typescript-eslint/explicit-module-boundary-types': 0,
41 |
42 | '@typescript-eslint/no-explicit-any': 0,
43 |
44 | '@typescript-eslint/type-annotation-spacing': 2,
45 |
46 | 'no-inner-declarations': 0,
47 |
48 | 'no-bitwise': 0,
49 |
50 | 'max-statements-per-line': ['error', { max: 1 }],
51 |
52 | 'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
53 |
54 | 'no-use-before-define': 0,
55 | '@typescript-eslint/no-use-before-define': 2,
56 |
57 | // for generic type parameters
58 | 'no-spaced-func': 0,
59 | 'func-call-spacing': 0,
60 | '@typescript-eslint/func-call-spacing': 2,
61 |
62 | // parameters with public/protected/private in class constructor
63 | 'no-unused-vars': 0,
64 | '@typescript-eslint/no-unused-vars': 2,
65 |
66 | indent: 0,
67 | '@typescript-eslint/indent': [
68 | 'error',
69 | 2,
70 | ],
71 |
72 | 'vue/block-lang': ['error',
73 | {
74 | script: {
75 | lang: 'ts',
76 | },
77 | },
78 | ],
79 |
80 | 'vue/component-name-in-template-casing': ['error', 'kebab-case', {
81 | registeredComponentsOnly: false,
82 | }],
83 |
84 | 'vue/no-export-in-script-setup': 2,
85 |
86 | 'vue/no-reserved-component-names': ['error', {
87 | disallowVue3BuiltInComponents: true,
88 | }],
89 |
90 | 'vue/no-template-target-blank': 2,
91 |
92 | 'vue/no-unregistered-components': 2,
93 |
94 | 'vue/no-unused-refs': 2,
95 |
96 | 'vue/no-useless-mustaches': 2,
97 |
98 | 'vue/no-useless-v-bind': 2,
99 |
100 | 'vue/no-v-text': 2,
101 |
102 | 'vue/padding-line-between-blocks': 2,
103 |
104 | 'vue/v-for-delimiter-style': ['error', 'of'],
105 |
106 | 'vue/valid-define-emits': 2,
107 |
108 | 'vue/valid-define-props': 2,
109 |
110 | 'vue/valid-next-tick': 2,
111 | },
112 | };
113 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-node@v2
11 | with:
12 | node-version: 16
13 | cache: 'yarn'
14 | - run: yarn
15 | - run: yarn build
16 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v2
15 | with:
16 | node-version: 16
17 | cache: 'yarn'
18 | - run: yarn
19 | - run: yarn test
20 | - run: yarn build
21 | - name: Deploy
22 | uses: peaceiris/actions-gh-pages@v3
23 | with:
24 | personal_token: ${{ secrets.GITHUB_TOKEN }}
25 | publish_dir: dist
26 | publish_branch: dist
27 | force_orphan: true
28 | commit_message: ${{ github.event.head_commit.message }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-node@v2
11 | with:
12 | node-version: 16
13 | cache: 'yarn'
14 | - run: yarn
15 | - run: yarn lint:nofix
16 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-node@v2
11 | with:
12 | node-version: 16
13 | cache: 'yarn'
14 | - run: yarn
15 | - run: yarn test --coverage
16 | - uses: codecov/codecov-action@v2
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | /coverage
6 |
7 | # local env files
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Truth Table Generator
2 |
3 | 离散数学 Open Project。
4 |
5 | 它现在不仅有真值表,还有主范式、最简范式、王浩算法、手动等值演算。
6 |
7 | [Demo](https://ouuan.github.io/truth-table-generator)
8 |
9 | ```bash
10 | yarn
11 | yarn serve
12 | yarn build
13 | ```
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Truth Table Generator - ouuan
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "truth-table-generator",
3 | "version": "0.0.1",
4 | "description": "Truth table generator",
5 | "repository": "https://github.com/ouuan/truth-table-generator",
6 | "author": "Yufan You (https://github.com/ouuan)",
7 | "license": "Apache-2.0",
8 | "private": true,
9 | "scripts": {
10 | "lint": "yarn lint:nofix --fix",
11 | "lint:nofix": "eslint . --ignore-path .gitignore --ext .ts,.vue,.js",
12 | "lint:types": "vue-tsc --noEmit",
13 | "serve": "vite",
14 | "build": "yarn lint && yarn lint:types && vite build",
15 | "test": "jest"
16 | },
17 | "dependencies": {
18 | "naive-ui": "^2.21.1",
19 | "ts-exhaustive-check": "^1.0.0",
20 | "vue": "^3.2.23"
21 | },
22 | "devDependencies": {
23 | "@types/jest": "^27.0.3",
24 | "@typescript-eslint/eslint-plugin": "^5.4.0",
25 | "@typescript-eslint/parser": "^5.4.0",
26 | "@vitejs/plugin-vue": "^1.10.1",
27 | "@vue/eslint-config-typescript": "^9.1.0",
28 | "eslint": "^8.3.0",
29 | "eslint-config-airbnb-base": "^15.0.0",
30 | "eslint-plugin-import": "^2.25.3",
31 | "eslint-plugin-vue": "^8.1.1",
32 | "jest": "^27.3.1",
33 | "ts-jest": "^27.0.7",
34 | "typescript": "^4.5.2",
35 | "vite": "^2.6.14",
36 | "vue-tsc": "^0.29.6"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
25 |
--------------------------------------------------------------------------------
/src/components/AboutSection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | by.
5 |
6 | ouuan
7 |
8 |
9 |
10 |
11 | Source Code @ GitHub
12 |
13 |
14 |
15 | Built with
16 |
17 | Vue 3
18 |
19 | and
20 |
21 | Naive UI
22 |
23 |
24 |
25 | 是离散数学这门课的选做作业,用的教材是
26 |
27 | 石纯一、王家廞:《数理逻辑与集合论(第二版)》
28 |
29 |
30 |
31 |
32 |
33 |
40 |
--------------------------------------------------------------------------------
/src/components/InputTips.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 运算符的种类,表示方法,以及优先级顺序:
7 |
8 | 括号(这些括号作用都相同): (), [], {}, (), 【】, {}
9 | 非: ¬, !, !, ~, ~, NOT, \lnot, \neg
10 | 与: ∧, &, &&, AND, \land, \wedge
11 | 与非: ↑, ⊼, NAND, \nand, \uparrow
12 | 异或: ⊕, ^, ⊻, !=, ……, XOR, \xor
13 | 或: ∨, |, |, ||, OR, \lor, \vee
14 | 或非: ↓, ⊽, NOR, \nor, \downarrow
15 | 蕴含(右结合): →, >, ->, 》, IMPLIES, \to, \rightarrow
16 | 被蕴含: ←, <, <-, 《, IMPLIEDBY, \gets, \leftarrow
17 | 等价: ↔, ⟷, =, ==, <->, <>, EQ, \leftrightarrow
18 |
19 |
20 | 非: !,与: &,或: |,蕴含: >,等价: =
21 |
22 |
23 | T/F 或 true/false 表示真/假
24 | 命题变项用除了 T/F 的单个大写字母表示
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
51 |
--------------------------------------------------------------------------------
/src/components/MainPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
18 |
22 |
23 |
24 |
28 |
29 |
34 |
39 |
40 |
41 |
42 | 规范的表达式: {{ rawRoot }}
43 |
44 | 将输入规范化
45 |
46 |
47 |
48 |
49 |
54 |
55 | 如果太长了,式子会被省略,点击式子就可以全部显示。
56 |
57 |
61 |
62 |
67 |
68 |
69 |
74 |
75 |
76 |
80 | 撤销
81 |
82 |
83 |
84 |
89 |
90 | 可以点击表头中的按钮来进行等值演算。如果没有发现你想要的规则,很可能是要多用几次交换律。
91 |
92 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
232 |
233 |
243 |
--------------------------------------------------------------------------------
/src/components/NormalForms.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 主合取范式 =
9 |
10 | ⋀
11 | {{ pnf.pcnfSub }} =
12 |
13 | {{ pnf.pcnf }}
14 |
15 |
16 |
17 |
22 | 主析取范式 =
23 |
24 | ⋁
25 | {{ pnf.pdnfSub }} =
26 |
27 | {{ pnf.pdnf }}
28 |
29 |
30 |
31 |
32 |
37 | 最简合取范式 =
38 | {{ mnf.cnf }}
39 |
40 |
41 |
42 |
47 | 最简析取范式 =
48 | {{ mnf.dnf }}
49 |
50 |
51 |
52 |
53 | 命题变项太多了,最简范式算不过来了 😢
54 |
55 |
56 |
57 |
120 |
--------------------------------------------------------------------------------
/src/components/SimplificationButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ node }}
4 |
5 |
10 | 你要将 {{ node }} 替换成?
11 |
16 | {{ option.result }} ({{ option.name }})
17 |
18 |
19 |
20 |
21 |
86 |
--------------------------------------------------------------------------------
/src/components/SimplificationSteps.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | {{ index ? '=' : '' }} {{ step.exp }} {{ step.rule ? `(${step.rule})` : '' }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
27 |
--------------------------------------------------------------------------------
/src/components/WangHaoProof.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 点击左侧三角可以展开定理推演过程,式子的颜色表示是否是重言式(定理),每行最右侧是下一步所使用的规则。
4 |
12 |
13 |
14 | 式子过长,王浩算法的步骤太多了 😫
15 |
16 |
17 |
18 |
58 |
59 |
74 |
--------------------------------------------------------------------------------
/src/core/AstNode/AndNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'and' as const;
6 |
7 | operator = '∧';
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | const r = this.children[1].dfsTruth(data);
16 | return this.children[0].dfsTruth(data) && r;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/AstNode/AtomNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 |
3 | export default class extends AstNode {
4 | type = 'atom' as const;
5 |
6 | operator = '';
7 |
8 | constructor(name: string) {
9 | super();
10 | this.str = name;
11 | }
12 |
13 | // actually unused
14 | calc() {
15 | return !!this.type;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/core/AstNode/EqNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'eq' as const;
6 |
7 | operator = '↔';
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | return this.children[0].dfsTruth(data) === this.children[1].dfsTruth(data);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/core/AstNode/FalseNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 |
3 | export default class extends AstNode {
4 | type = 'false' as const;
5 |
6 | operator = '';
7 |
8 | constructor() {
9 | super();
10 | this.str = 'F';
11 | }
12 |
13 | calc() {
14 | return !this.type;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/core/AstNode/ImpliedByNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'impliedby' as const;
6 |
7 | operator = '←' as const;
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | const l = this.children[0].dfsTruth(data);
16 | return !this.children[1].dfsTruth(data) || l;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/AstNode/ImplyNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'imply' as const;
6 |
7 | operator = '→' as const;
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | const r = this.children[1].dfsTruth(data);
16 | return !this.children[0].dfsTruth(data) || r;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/AstNode/NandNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'nand' as const;
6 |
7 | operator = '↑' as const;
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | const r = this.children[1].dfsTruth(data);
16 | return !(this.children[0].dfsTruth(data) && r);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/AstNode/NorNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'nor' as const;
6 |
7 | operator = '↓' as const;
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | const r = this.children[1].dfsTruth(data);
16 | return !(this.children[0].dfsTruth(data) || r);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/AstNode/NotNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'not' as const;
6 |
7 | operator = '¬';
8 |
9 | constructor(internal: AstNode) {
10 | super();
11 | this.children.push(internal);
12 | }
13 |
14 | calc(data: Data) {
15 | return !this.children[0].dfsTruth(data);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/core/AstNode/OrNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'or' as const;
6 |
7 | operator = '∨';
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | const r = this.children[1].dfsTruth(data);
16 | return this.children[0].dfsTruth(data) || r;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/AstNode/TrueNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 |
3 | export default class extends AstNode {
4 | type = 'true' as const;
5 |
6 | operator = '';
7 |
8 | constructor() {
9 | super();
10 | this.str = 'T';
11 | }
12 |
13 | calc() {
14 | return !!this.type;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/core/AstNode/XorNode.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import Data from '~/types/data';
3 |
4 | export default class extends AstNode {
5 | type = 'xor' as const;
6 |
7 | operator = '⊕' as const;
8 |
9 | constructor(left: AstNode, right: AstNode) {
10 | super();
11 | this.children.push(left, right);
12 | }
13 |
14 | calc(data: Data) {
15 | return this.children[0].dfsTruth(data) !== this.children[1].dfsTruth(data);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/core/AstNode/base.ts:
--------------------------------------------------------------------------------
1 | import Data from '~/types/data';
2 |
3 | import precedence from './precedence';
4 |
5 | abstract class AstNode {
6 | abstract type: 'atom' | 'not' | 'and' | 'nand' | 'xor' | 'or' | 'nor' | 'imply' | 'impliedby' | 'eq' | 'true' | 'false';
7 |
8 | abstract operator: string;
9 |
10 | children: AstNode[] = [];
11 |
12 | ch(index: number) {
13 | return this.children[index];
14 | }
15 |
16 | static quote(u: AstNode, v: AstNode): string {
17 | if (precedence[u.type] < precedence[v.type] + 1) {
18 | return `(${v})`;
19 | }
20 | return v.toString();
21 | }
22 |
23 | protected str = '';
24 |
25 | updateStr() {
26 | this.children.forEach((v) => v.updateStr());
27 | if (this.children.length === 1) {
28 | this.str = `${this.operator}${AstNode.quote(this, this.children[0])}`;
29 | } else if (this.children.length === 2) {
30 | this.str = `${AstNode.quote(this, this.children[0])} ${this.operator} ${AstNode.quote(this, this.children[1])}`;
31 | }
32 | }
33 |
34 | toString(): string {
35 | return this.str;
36 | }
37 |
38 | abstract calc(data: Data): boolean;
39 |
40 | dfsTruth(data: Data): boolean {
41 | if (data[this.str] === undefined) {
42 | /* eslint-disable no-param-reassign */
43 | data[this.str] = this.calc(data) ? 1 : 0;
44 | }
45 | return !!data[this.str];
46 | }
47 | }
48 |
49 | export default AstNode;
50 |
--------------------------------------------------------------------------------
/src/core/AstNode/index.ts:
--------------------------------------------------------------------------------
1 | import AstNode from './base';
2 | import AtomNode from './AtomNode';
3 | import NotNode from './NotNode';
4 | import AndNode from './AndNode';
5 | import NandNode from './NandNode';
6 | import XorNode from './XorNode';
7 | import OrNode from './OrNode';
8 | import NorNode from './NorNode';
9 | import ImplyNode from './ImplyNode';
10 | import ImpliedByNode from './ImpliedByNode';
11 | import EqNode from './EqNode';
12 | import TrueNode from './TrueNode';
13 | import FalseNode from './FalseNode';
14 |
15 | export {
16 | AstNode,
17 | AtomNode,
18 | NotNode,
19 | AndNode,
20 | NandNode,
21 | XorNode,
22 | OrNode,
23 | NorNode,
24 | ImplyNode,
25 | ImpliedByNode,
26 | EqNode,
27 | TrueNode,
28 | FalseNode,
29 | };
30 |
--------------------------------------------------------------------------------
/src/core/AstNode/precedence.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | eq: 5,
3 | impliedby: 4.1,
4 | imply: 4,
5 | nor: 3.1,
6 | or: 3,
7 | xor: 2.5,
8 | nand: 2.1,
9 | and: 2,
10 | not: 1,
11 | atom: 0,
12 | true: 0,
13 | false: 0,
14 | } as const;
15 |
--------------------------------------------------------------------------------
/src/core/QuineMcCluskey/Minterm.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A class to hold information about a minterm when using the Quine-McCluskey Algorithm
3 | */
4 | export default class Minterm {
5 | values: number[];
6 |
7 | value: string;
8 |
9 | used: boolean;
10 |
11 | /**
12 | * Creates a new minterm object
13 | */
14 | constructor(values: number[], value: string) {
15 | this.values = values;
16 | this.value = value;
17 | this.used = false;
18 |
19 | this.values.sort();
20 | }
21 |
22 | /**
23 | * Returns a String representation of the Minterm
24 | */
25 | toString() {
26 | const values = this.values.join(', ');
27 | return `m(${values}) = ${this.value}`;
28 | }
29 |
30 | /**
31 | * Determines if this Minterm object is equal to another object
32 | */
33 | equals(minterm: Minterm) {
34 | if (!(minterm instanceof Minterm)) {
35 | return false;
36 | }
37 |
38 | return (
39 | this.value === minterm.value
40 | && this.values.length === minterm.values.length
41 | && this.values.every((u, i) => u === minterm.values[i])
42 | );
43 | }
44 |
45 | /**
46 | * Returns the values in this Minterm
47 | */
48 | getValues() {
49 | return this.values;
50 | }
51 |
52 | /**
53 | * Returns the binary value of this Minterm
54 | */
55 | getValue() {
56 | return this.value;
57 | }
58 |
59 | /**
60 | * Returns whether or not this Minterm has been used
61 | */
62 | isUsed() {
63 | return this.used;
64 | }
65 |
66 | /**
67 | * Labels this Minterm as "used"
68 | */
69 | use() {
70 | this.used = true;
71 | }
72 |
73 | /**
74 | * Combines 2 Minterms together if they can be combined
75 | */
76 | combine(minterm: Minterm) {
77 | // Check if this value is this same; If so, do nothing
78 | if (this.value === minterm.value) {
79 | return null;
80 | }
81 |
82 | // Check if the values are the same; If so, do nothing
83 | if (
84 | this.values.length === minterm.values.length
85 | && this.values.every((u, i) => u === minterm.values[i])
86 | ) {
87 | return null;
88 | }
89 |
90 | // Keep track of the difference between the minterms
91 | let diff = 0;
92 | let result = '';
93 |
94 | // Iterate through the bits in this Minterm's value
95 | for (let i = 0; i < this.value.length; i += 1) {
96 | // Check if the current bit value differs from the minterm's bit value
97 | if (this.value.charAt(i) !== minterm.value.charAt(i)) {
98 | diff += 1;
99 | result += '-';
100 | } else {
101 | // There is no difference
102 | result += this.value.charAt(i);
103 | }
104 |
105 | // The difference has exceeded 1
106 | if (diff > 1) {
107 | return null;
108 | }
109 | }
110 |
111 | return new Minterm(this.values.concat(minterm.values), result);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/core/QuineMcCluskey/QuineMcCluskey.ts:
--------------------------------------------------------------------------------
1 | import Minterm from './Minterm';
2 | import { decToBin, valueIn } from './utils';
3 |
4 | /**
5 | * A class to handle processing the Quine-McCluskey Algorithm
6 | */
7 | export default class QuineMcCluskey {
8 | variables: string;
9 |
10 | values: number[];
11 |
12 | allValues: number[];
13 |
14 | dontCares: number[];
15 |
16 | isMaxterm: boolean;
17 |
18 | func: string;
19 |
20 | /**
21 | * Creates a new QuineMcCluskey object to process the Quine-Mccluskey Algorithm
22 | */
23 | constructor(
24 | variables: string,
25 | values: number[],
26 | dontCares: number[] = [],
27 | isMaxterm = false,
28 | ) {
29 | values.sort();
30 | this.variables = variables;
31 | this.values = values;
32 | this.allValues = values.concat(dontCares);
33 | this.allValues.sort();
34 | this.dontCares = dontCares;
35 | this.isMaxterm = isMaxterm;
36 | this.func = this.getFunction() || '';
37 | }
38 |
39 | // Helper Methods
40 |
41 | /**
42 | * Returns the binary value equivalent to the decimal value given
43 | */
44 | getBits(value: number) {
45 | let s = (value >>> 0).toString(2);
46 | for (let i = s.length; i < this.variables.length; i += 1) s = `0${s}`;
47 | return s;
48 | }
49 |
50 | // Grouping Methods
51 |
52 | /**
53 | * Creates the initial grouping for the bits from the values
54 | * given to the Quine-McCluskey Algorithm
55 | */
56 | initialGroup() {
57 | // Keep track of groups by 2-dimensional array
58 | const groups: Minterm[][] = [];
59 | for (let i = 0; i < this.variables.length + 1; i += 1) {
60 | groups.push([]);
61 | }
62 |
63 | // Iterate through values
64 | for (const value of this.allValues) {
65 | // Count number of 1's in value's bit equivalent
66 | let count = 0;
67 | const bits = this.getBits(value);
68 | for (const bit of bits) {
69 | if (bit === '1') {
70 | count += 1;
71 | }
72 | }
73 |
74 | // Add count to proper group
75 | groups[count].push(new Minterm([value], bits));
76 | }
77 |
78 | return groups;
79 | }
80 |
81 | /**
82 | * Creates a power set of all valid prime implicants that covers the rest of an expression.
83 | * This is used after the essential prime implicants have been found.
84 | */
85 | static powerSet(values: number[], primeImplicants: Minterm[]) {
86 | // Get the power set of all the prime implicants
87 | let powerset = [];
88 |
89 | // Iterate through decimal values from 1 to 2 ** size - 1
90 | for (let i = 1; i < 2 ** primeImplicants.length - 1; i += 1) {
91 | const currentset = [];
92 |
93 | // Get the binary value of the decimal value
94 | let binValue = decToBin(i);
95 | for (let j = binValue.length; j < primeImplicants.length; j += 1) {
96 | binValue = `0${binValue}`;
97 | }
98 |
99 | // Find which indexes have 1 in the binValue string
100 | for (let j = 0; j < binValue.length; j += 1) {
101 | if (binValue.charAt(j) === '1') {
102 | currentset.push(primeImplicants[j]);
103 | }
104 | }
105 | powerset.push(currentset);
106 | }
107 |
108 | // Remove all subsets that do not cover the rest of the implicants
109 | const newpowerset = [];
110 | for (const subset of powerset) {
111 | // Get all the values the set covers
112 | const tempValues = [];
113 | for (const implicant of subset) {
114 | for (const value of implicant.getValues()) {
115 | if (!valueIn(value, tempValues) && valueIn(value, values)) {
116 | tempValues.push(value);
117 | }
118 | }
119 | }
120 | tempValues.sort();
121 |
122 | // Check if this subset covers the rest of the values
123 | if (
124 | tempValues.length === values.length
125 | && tempValues.every((u, i) => u === values[i])
126 | ) {
127 | newpowerset.push(subset);
128 | }
129 | }
130 | powerset = newpowerset;
131 |
132 | // Find the minimum amount of implicants that can cover the expression
133 | let minSet = powerset[0];
134 | for (const subset of powerset) {
135 | if (subset.length < minSet.length) {
136 | minSet = subset;
137 | }
138 | }
139 |
140 | if (minSet === undefined) {
141 | return [];
142 | }
143 | return minSet;
144 | }
145 |
146 | // Compare Methods
147 |
148 | /**
149 | * Returns an array of all the prime implicants for an expression
150 | */
151 | getPrimeImplicants(groups = this.initialGroup()) {
152 | // If there is only 1 group, return all the minterms in it
153 | if (groups.length === 1) {
154 | return groups[0];
155 | }
156 |
157 | // Try comparing the rest
158 |
159 | const unused: Minterm[] = [];
160 | const comparisons = [...Array(groups.length - 1).keys()];
161 | const newGroups: Minterm[][] = comparisons.map(() => []);
162 |
163 | // Compare each adjacent group
164 | for (const compare of comparisons) {
165 | const group1 = groups[compare];
166 | const group2 = groups[compare + 1];
167 |
168 | // Compare every term in group1 with every term in group2
169 | for (const term1 of group1) {
170 | for (const term2 of group2) {
171 | // Try combining it
172 | const term3 = term1.combine(term2);
173 |
174 | // Only add it to the new group if term3 is not null
175 | // term3 will only be null if term1 and term2 could not
176 | // be combined
177 | if (term3 !== null) {
178 | term1.use();
179 | term2.use();
180 | if (!valueIn(term3, newGroups[compare])) {
181 | newGroups[compare].push(term3);
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
188 | // Get array of all unused minterms
189 | for (const group of groups) {
190 | for (const term of group) {
191 | if (!term.isUsed() && !valueIn(term, unused)) {
192 | unused.push(term);
193 | }
194 | }
195 | }
196 |
197 | // Add recursive call
198 | for (const term of this.getPrimeImplicants(newGroups)) {
199 | if (!term.isUsed() && !valueIn(term, unused)) {
200 | unused.push(term);
201 | }
202 | }
203 |
204 | return unused;
205 | }
206 |
207 | // Solving Methods
208 |
209 | /**
210 | * Solves for the expression returning the minimal amount of prime implicants needed
211 | * to cover the expression.
212 | */
213 | solve() {
214 | // Get the prime implicants
215 | let primeImplicants = this.getPrimeImplicants();
216 |
217 | // Keep track of values with only 1 implicant
218 | // These are the essential prime implicants
219 | const essentialPrimeImplicants = [];
220 | const valuesUsed = [];
221 | for (let i = 0; i < this.values.length; i += 1) {
222 | valuesUsed.push(false);
223 | }
224 |
225 | // Iterate through values
226 | for (let i = 0; i < this.values.length; i += 1) {
227 | const value = this.values[i];
228 |
229 | let uses = 0;
230 | let last = null;
231 | for (const minterm of primeImplicants) {
232 | if (valueIn(value, minterm.getValues())) {
233 | uses += 1;
234 | last = minterm;
235 | }
236 | }
237 | if (uses === 1 && last && !valueIn(last, essentialPrimeImplicants)) {
238 | for (const v of last.getValues()) {
239 | if (!valueIn(v, this.dontCares)) {
240 | valuesUsed[this.values.indexOf(v)] = true;
241 | }
242 | }
243 | essentialPrimeImplicants.push(last);
244 | }
245 | }
246 |
247 | // Check if all values were used
248 | let found = false;
249 | for (const value of valuesUsed) {
250 | if (!value) {
251 | found = true;
252 | break;
253 | }
254 | }
255 | if (!found) {
256 | return essentialPrimeImplicants;
257 | }
258 |
259 | // Keep track of prime implicants that cover as many values as possible
260 | // with as few variables as possible
261 | const newPrimeImplicants = [];
262 | for (const implicant of primeImplicants) {
263 | if (!valueIn(implicant, essentialPrimeImplicants)) {
264 | // Check if the current implicant only consists of dont cares
265 | let add = false;
266 | for (const value of implicant.getValues()) {
267 | if (!valueIn(value, this.dontCares)) {
268 | add = true;
269 | break;
270 | }
271 | }
272 | if (add) {
273 | newPrimeImplicants.push(implicant);
274 | }
275 | }
276 | }
277 | primeImplicants = newPrimeImplicants;
278 |
279 | // Check if there is only 1 implicant left (very rare but just in case)
280 | if (primeImplicants.length === 1) {
281 | return essentialPrimeImplicants.concat(primeImplicants);
282 | }
283 |
284 | // Create a power set from the remaining prime implicants and check which
285 | // combination of prime implicants gets the simplest form
286 | const newValues = [];
287 | for (let i = 0; i < this.values.length; i += 1) {
288 | if (!valuesUsed[i]) {
289 | newValues.push(this.values[i]);
290 | }
291 | }
292 |
293 | return essentialPrimeImplicants.concat(
294 | QuineMcCluskey.powerSet(newValues, primeImplicants),
295 | );
296 | }
297 |
298 | /**
299 | * Returns the expression in a readable form
300 | */
301 | getFunction() {
302 | // Check if function already exists, return it
303 | if (this.func != null) {
304 | return this.func;
305 | }
306 |
307 | // Get the prime implicants and variables
308 | const primeImplicants = this.solve();
309 |
310 | // Check if there are no prime implicants; Always False
311 | if (primeImplicants.length === 0) {
312 | return this.isMaxterm ? 'T' : 'F';
313 | }
314 |
315 | if (primeImplicants.length === 1) {
316 | let count = 0;
317 | for (const index of primeImplicants[0].getValue()) {
318 | if (index === '-') {
319 | count += 1;
320 | }
321 | }
322 | if (count === this.variables.length) {
323 | return this.isMaxterm ? 'F' : 'T';
324 | }
325 | }
326 |
327 | let result = '';
328 |
329 | // Iterate through the prime implicants
330 | for (let i = 0; i < primeImplicants.length; i += 1) {
331 | const implicant = primeImplicants[i];
332 |
333 | // Add parentheses if necessary
334 | if (
335 | (implicant.getValue().match(/-/g) || []).length
336 | < this.variables.length - 1
337 | ) {
338 | result += '(';
339 | }
340 |
341 | // Iterate through all the bits in the implicants value
342 | for (let j = 0; j < implicant.getValue().length; j += 1) {
343 | if (implicant.getValue().charAt(j) === (this.isMaxterm ? '1' : '0')) {
344 | result += '¬';
345 | }
346 | if (implicant.getValue().charAt(j) !== '-') {
347 | result += this.variables[j];
348 | }
349 | if (
350 | (
351 | implicant
352 | .getValue()
353 | .substring(j + 1)
354 | .match(/-/g) || []
355 | ).length
356 | < implicant.getValue().length - j - 1
357 | && implicant.getValue().charAt(j) !== '-'
358 | ) {
359 | result += this.isMaxterm ? ' ∨ ' : ' ∧ ';
360 | }
361 | }
362 |
363 | // Add parentheses if necessary
364 | if (
365 | (implicant.getValue().match(/-/g) || []).length
366 | < this.variables.length - 1
367 | ) {
368 | result += ')';
369 | }
370 |
371 | // Combine minterms with an OR operator
372 | if (i < primeImplicants.length - 1) {
373 | result += this.isMaxterm ? ' ∧ ' : ' ∨ ';
374 | }
375 | }
376 |
377 | return result;
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/src/core/QuineMcCluskey/index.ts:
--------------------------------------------------------------------------------
1 | import QuineMcCluskey from './QuineMcCluskey';
2 |
3 | export default QuineMcCluskey;
4 |
--------------------------------------------------------------------------------
/src/core/QuineMcCluskey/utils.ts:
--------------------------------------------------------------------------------
1 | import Minterm from './Minterm';
2 |
3 | /**
4 | * Converts a value from decimal to binary
5 | *
6 | * @param value The value to convert to binary
7 | */
8 | export function decToBin(value: number) {
9 | return (value >>> 0).toString(2);
10 | }
11 |
12 | /**
13 | * Returns whether or not a value is in an array
14 | *
15 | * @param value The value to look for in an array
16 | * @param array The array to look for a value in
17 | */
18 | export function valueIn(value: number | Minterm, array: (number|Minterm)[]) {
19 | return Boolean(array.find((item) => {
20 | if (value instanceof Minterm && item instanceof Minterm) {
21 | return value.equals(item);
22 | }
23 | return item === value;
24 | }));
25 | }
26 |
--------------------------------------------------------------------------------
/src/core/WangHao.ts:
--------------------------------------------------------------------------------
1 | import { exhaustiveCheck } from 'ts-exhaustive-check';
2 | import { NText, TreeOption } from 'naive-ui';
3 | import { h } from 'vue';
4 |
5 | import { AstNode } from './AstNode';
6 | import equivalents from './equivalents';
7 | import WangHaoTooLongError from '~/types/WangHaoTooLongError';
8 |
9 | interface WangHaoProof extends TreeOption {
10 | key: string;
11 | label: string;
12 | isTrue: boolean;
13 | children?: WangHaoProof[];
14 | }
15 |
16 | export default class WangHao {
17 | private left = new Set();
18 |
19 | private right = new Set();
20 |
21 | private leftStr = new Set();
22 |
23 | private rightStr = new Set();
24 |
25 | private isTrue = false;
26 |
27 | private tot: { value: number };
28 |
29 | key: number;
30 |
31 | private rule = '';
32 |
33 | constructor(root: AstNode);
34 |
35 | constructor(old: WangHao);
36 |
37 | constructor(x: AstNode | WangHao) {
38 | if (x instanceof AstNode) {
39 | this.leftStr.add('T');
40 | this.rightStr.add('F');
41 | this.rightStr.add(x.toString());
42 | if (x.toString().length > 1) {
43 | this.right.add(x);
44 | }
45 | if (x.type === 'true') {
46 | this.isTrue = true;
47 | this.rule = 'T ⇒ T';
48 | }
49 | this.key = 1;
50 | this.tot = { value: 1 };
51 | } else {
52 | for (const l of x.left) this.left.add(l);
53 | for (const r of x.right) this.right.add(r);
54 | for (const l of x.leftStr) this.leftStr.add(l);
55 | for (const r of x.rightStr) this.rightStr.add(r);
56 | this.tot = x.tot;
57 | this.tot.value += 1;
58 | if (this.tot.value > 1e4) throw new WangHaoTooLongError();
59 | this.key = x.tot.value;
60 | }
61 | }
62 |
63 | toString(): string {
64 | const lItems = [];
65 | const rItems = [];
66 |
67 | for (const l of this.leftStr.values()) {
68 | if (l !== 'T') {
69 | lItems.push(l);
70 | }
71 | }
72 | if (lItems.length === 0) lItems.push('T');
73 |
74 | for (const r of this.rightStr.values()) {
75 | if (r !== 'F') {
76 | rItems.push(r);
77 | }
78 | }
79 | if (rItems.length === 0) rItems.push('F');
80 |
81 | return `${lItems.join(', ')} ⇒ ${rItems.join(', ')}`;
82 | }
83 |
84 | private del(node: AstNode, side: 'left' | 'right') {
85 | this[side].delete(node);
86 | this[`${side}Str`].delete(node.toString());
87 | }
88 |
89 | private add(node: AstNode, side: 'left' | 'right') {
90 | if (this[`${side}Str`].has(node.toString())) return;
91 | this[`${side}Str`].add(node.toString());
92 | if (node.toString().length > 1) this[side].add(node);
93 | if (this[side === 'left' ? 'rightStr' : 'leftStr'].has(node.toString())) {
94 | this.isTrue = true;
95 | this.rule = `${node} ⇒ ${node}`;
96 | }
97 | }
98 |
99 | solve(): WangHaoProof {
100 | const children: WangHaoProof[] = [];
101 |
102 | if (!this.isTrue) {
103 | if (this.left.size > 0) {
104 | const node: AstNode = this.left.values().next().value;
105 |
106 | const a = new WangHao(this);
107 | a.del(node, 'left');
108 |
109 | this.rule = `${node.operator} ⇒`;
110 |
111 | switch (node.type) {
112 | case 'not':
113 | {
114 | a.add(node.ch(0), 'right');
115 | children.push(a.solve());
116 | break;
117 | }
118 | case 'and':
119 | {
120 | a.add(node.ch(0), 'left');
121 | a.add(node.ch(1), 'left');
122 | children.push(a.solve());
123 | break;
124 | }
125 | case 'or':
126 | {
127 | const b = new WangHao(a);
128 | a.add(node.ch(0), 'left');
129 | b.add(node.ch(1), 'left');
130 | children.push(a.solve(), b.solve());
131 | break;
132 | }
133 | case 'imply':
134 | {
135 | const b = new WangHao(a);
136 | a.add(node.ch(1), 'left');
137 | b.add(node.ch(0), 'right');
138 | children.push(a.solve(), b.solve());
139 | break;
140 | }
141 | case 'eq':
142 | {
143 | const b = new WangHao(a);
144 | a.add(node.ch(0), 'left');
145 | a.add(node.ch(1), 'left');
146 | b.add(node.ch(0), 'right');
147 | b.add(node.ch(1), 'right');
148 | children.push(a.solve(), b.solve());
149 | break;
150 | }
151 | case 'nor':
152 | case 'xor':
153 | case 'nand':
154 | case 'impliedby':
155 | {
156 | const { result } = equivalents(node)[0];
157 | result.updateStr();
158 | a.add(result, 'left');
159 | children.push(a.solve());
160 | break;
161 | }
162 | case 'atom':
163 | case 'true':
164 | case 'false':
165 | throw Error(`${node.type} appears in WangHao's left!`);
166 | default:
167 | exhaustiveCheck(node.type);
168 | }
169 | } else if (this.right.size > 0) {
170 | const node: AstNode = this.right.values().next().value;
171 |
172 | const a = new WangHao(this);
173 | a.del(node, 'right');
174 |
175 | this.rule = `⇒ ${node.operator}`;
176 |
177 | switch (node.type) {
178 | case 'not':
179 | {
180 | a.add(node.ch(0), 'left');
181 | children.push(a.solve());
182 | break;
183 | }
184 | case 'and':
185 | {
186 | const b = new WangHao(a);
187 | a.add(node.ch(0), 'right');
188 | b.add(node.ch(1), 'right');
189 | children.push(a.solve(), b.solve());
190 | break;
191 | }
192 | case 'or':
193 | {
194 | a.add(node.ch(0), 'right');
195 | a.add(node.ch(1), 'right');
196 | children.push(a.solve());
197 | break;
198 | }
199 | case 'imply':
200 | {
201 | a.add(node.ch(0), 'left');
202 | a.add(node.ch(1), 'right');
203 | children.push(a.solve());
204 | break;
205 | }
206 | case 'eq':
207 | {
208 | const b = new WangHao(a);
209 | a.add(node.ch(0), 'left');
210 | a.add(node.ch(1), 'right');
211 | b.add(node.ch(0), 'right');
212 | b.add(node.ch(1), 'left');
213 | children.push(a.solve(), b.solve());
214 | break;
215 | }
216 | case 'nor':
217 | case 'xor':
218 | case 'nand':
219 | case 'impliedby':
220 | {
221 | const { result } = equivalents(node)[0];
222 | result.updateStr();
223 | a.add(result, 'right');
224 | children.push(a.solve());
225 | break;
226 | }
227 | case 'atom':
228 | case 'true':
229 | case 'false':
230 | throw Error(`${node.type} appears in WangHao's right!`);
231 | default:
232 | exhaustiveCheck(node.type);
233 | }
234 | }
235 |
236 | if (children.length) {
237 | this.isTrue = true;
238 | children.forEach(({ isTrue }) => {
239 | if (!isTrue) {
240 | this.isTrue = false;
241 | }
242 | });
243 | }
244 | }
245 |
246 | return {
247 | key: `${Number(this.isTrue)}-${this.key}-wh-${new Date().valueOf()}`,
248 | label: this.toString(),
249 | isTrue: this.isTrue,
250 | children: children.length === 0 ? undefined : children,
251 | isLeaf: children.length === 0,
252 | suffix: () => h(NText, {
253 | type: 'info',
254 | class: 'wanghao-rule',
255 | }, {
256 | default: () => (this.rule ? this.rule : undefined),
257 | }),
258 | };
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/src/core/buildAst.ts:
--------------------------------------------------------------------------------
1 | import { exhaustiveCheck } from 'ts-exhaustive-check';
2 | import {
3 | AstNode,
4 | AtomNode,
5 | NotNode,
6 | AndNode,
7 | NandNode,
8 | XorNode,
9 | OrNode,
10 | NorNode,
11 | ImplyNode,
12 | ImpliedByNode,
13 | EqNode,
14 | TrueNode,
15 | FalseNode,
16 | } from './AstNode';
17 |
18 | import transformExp from './transformExp';
19 |
20 | const precedence = {
21 | '=': 5,
22 | '<': 4.1,
23 | '>': 4,
24 | '↓': 3.1,
25 | '|': 3,
26 | '^': 2.5,
27 | '↑': 2.1,
28 | '&': 2,
29 | } as const;
30 |
31 | export default function buildAst(expression: string): {
32 | root: AstNode,
33 | atomNodes: Map
34 | } | null {
35 | try {
36 | const exp = transformExp(expression);
37 |
38 | if (/[^A-Z()!&|><=↑^↓]/.test(exp)) return null;
39 |
40 | const stacks: (AstNode | '&' | '↑' | '^' | '↓' | '|' | '>' | '<' | '=' | '!')[][] = [[]];
41 |
42 | function current() {
43 | return stacks[stacks.length - 1];
44 | }
45 |
46 | function isBinaryOperator(x: unknown): x is '&' | '↑' | '^' | '↓' | '|' | '>' | '<' | '=' {
47 | return typeof x === 'string' && /^[&↑^↓|><=]$/.test(x);
48 | }
49 |
50 | function push(x: (typeof stacks)[number][number]) {
51 | if (current().length === 0) {
52 | current().push(x);
53 | return;
54 | }
55 | const last = current()[current().length - 1];
56 | function error() {
57 | return `Invalid expression: "${x}" after "${last}"`;
58 | }
59 | if (last === '!') {
60 | if (isBinaryOperator(x)) throw error();
61 | if (x === '!') current().push('!');
62 | else {
63 | current().pop();
64 | push(new NotNode(x));
65 | }
66 | } else if (isBinaryOperator(last)) {
67 | if (isBinaryOperator(x)) throw error();
68 | current().push(x);
69 | } else if (isBinaryOperator(x)) current().push(x);
70 | else throw error();
71 | }
72 |
73 | function reduce() {
74 | const error = new Error('Reduce error');
75 |
76 | const right = current().pop();
77 | if (typeof right !== 'object') throw error;
78 |
79 | const operator = current().pop();
80 | if (typeof operator !== 'string') throw error;
81 |
82 | const left = current().pop();
83 | if (typeof left !== 'object') throw error;
84 |
85 | switch (operator) {
86 | case '&':
87 | push(new AndNode(left, right));
88 | break;
89 | case '↑':
90 | push(new NandNode(left, right));
91 | break;
92 | case '^':
93 | push(new XorNode(left, right));
94 | break;
95 | case '↓':
96 | push(new NorNode(left, right));
97 | break;
98 | case '|':
99 | push(new OrNode(left, right));
100 | break;
101 | case '>':
102 | push(new ImplyNode(left, right));
103 | break;
104 | case '<':
105 | push(new ImpliedByNode(left, right));
106 | break;
107 | case '=':
108 | push(new EqNode(left, right));
109 | break;
110 | case '!':
111 | throw error;
112 | default:
113 | exhaustiveCheck(operator);
114 | }
115 | }
116 |
117 | const atomNodes: Map = new Map();
118 |
119 | for (let pos = 0; pos < exp.length; pos += 1) {
120 | const c = exp[pos];
121 | switch (c) {
122 | case '(':
123 | stacks.push([]);
124 | break;
125 | case ')': {
126 | while (current().length > 1) reduce();
127 | const res = current()[0];
128 | if (typeof res !== 'object') throw Error('End bracket error');
129 | stacks.pop();
130 | push(res);
131 | break;
132 | }
133 | case '&':
134 | case '↑':
135 | case '^':
136 | case '↓':
137 | case '|':
138 | case '>':
139 | case '<':
140 | case '=':
141 | {
142 | while (current().length > 1) {
143 | const operator = current()[current().length - 2];
144 | if (typeof operator !== 'string' || operator === '!') throw Error('Invalid stack');
145 | if (precedence[operator] < precedence[c] || (operator === c && c !== '>')) reduce();
146 | else break;
147 | }
148 | push(c);
149 | break;
150 | }
151 | case '!':
152 | push('!');
153 | break;
154 | case 'T':
155 | push(new TrueNode());
156 | break;
157 | case 'F':
158 | push(new FalseNode());
159 | break;
160 | default: {
161 | const node = new AtomNode(c);
162 | let nodes = atomNodes.get(c);
163 | if (!nodes) {
164 | nodes = [];
165 | atomNodes.set(c, nodes);
166 | }
167 | nodes.push(node);
168 | push(node);
169 | break;
170 | }
171 | }
172 | }
173 |
174 | if (stacks.length !== 1) throw Error('Bracket error');
175 |
176 | while (current().length > 1) reduce();
177 |
178 | const root = current()[0];
179 | if (typeof root === 'string') throw Error('Invalid root');
180 |
181 | root.updateStr();
182 |
183 | return {
184 | root,
185 | atomNodes,
186 | };
187 | } catch (error) {
188 | return null;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/core/equivalents.ts:
--------------------------------------------------------------------------------
1 | // Ideally this should be implemented in derived classes,
2 | // but that could lead to cyclic imports, which is tricky to resolve
3 |
4 | import { exhaustiveCheck } from 'ts-exhaustive-check';
5 | import Equivalent from '~/types/equivalent';
6 | import {
7 | AndNode,
8 | AstNode,
9 | EqNode,
10 | FalseNode,
11 | ImplyNode,
12 | NotNode,
13 | OrNode,
14 | TrueNode,
15 | } from './AstNode';
16 |
17 | export default function equivalents(node: AstNode): Equivalent[] {
18 | const result: Equivalent[] = [];
19 |
20 | switch (node.type) {
21 | case 'and':
22 | if (node.ch(0).type === node.type) {
23 | result.push({
24 | name: '结合律',
25 | result: new AndNode(node.ch(0).ch(0), new AndNode(node.ch(0).ch(1), node.ch(1))),
26 | });
27 | }
28 | if (node.ch(1).type === node.type) {
29 | result.push({
30 | name: '结合律',
31 | result: new AndNode(new AndNode(node.ch(0), node.ch(1).ch(0)), node.ch(1).ch(1)),
32 | });
33 | }
34 | result.push({
35 | name: '交换律',
36 | result: new AndNode(node.ch(1), node.ch(0)),
37 | });
38 | if (node.ch(0).type === 'or' && node.ch(1).type === 'or' && node.ch(0).ch(0).toString() === node.ch(1).ch(0).toString()) {
39 | result.push({
40 | name: '分配律',
41 | result: new OrNode(node.ch(0).ch(0), new AndNode(node.ch(0).ch(1), node.ch(1).ch(1))),
42 | });
43 | }
44 | if (node.ch(1).type === 'or') {
45 | result.push({
46 | name: '分配律',
47 | result: new OrNode(
48 | new AndNode(node.ch(0), node.ch(1).ch(0)),
49 | new AndNode(node.ch(0), node.ch(1).ch(1)),
50 | ),
51 | });
52 | }
53 | if (node.ch(0).toString() === node.ch(1).toString()) {
54 | result.push({
55 | name: '等幂律',
56 | result: node.ch(0),
57 | });
58 | }
59 | if (node.ch(1).type === 'or' && node.ch(0).toString() === node.ch(1).ch(0).toString()) {
60 | result.push({
61 | name: '吸收律',
62 | result: node.ch(0),
63 | });
64 | }
65 | if (node.ch(0).type === 'not' && node.ch(1).type === 'not') {
66 | result.push({
67 | name: '摩根律',
68 | result: new NotNode(new OrNode(node.ch(0).ch(0), node.ch(1).ch(0))),
69 | });
70 | }
71 | if (node.ch(1).type === 'not') {
72 | result.push({
73 | name: '摩根律',
74 | result: new NotNode(new ImplyNode(node.ch(0), node.ch(1).ch(0))),
75 | });
76 | }
77 | if (node.ch(1).type === 'true') {
78 | result.push({
79 | name: '同一律',
80 | result: node.ch(0),
81 | });
82 | }
83 | if (node.ch(1).type === 'false') {
84 | result.push({
85 | name: '零律',
86 | result: node.ch(1),
87 | });
88 | }
89 | if (node.ch(1).type === 'not' && node.ch(0).toString() === node.ch(1).ch(0).toString()) {
90 | result.push({
91 | name: '补余律',
92 | result: new FalseNode(),
93 | });
94 | }
95 | if (node.ch(0).type === 'imply' && node.ch(1).type === 'imply' && node.ch(0).ch(1).toString() === node.ch(1).ch(1).toString()) {
96 | result.push({
97 | name: '前提析取合并',
98 | result: new ImplyNode(new OrNode(node.ch(0).ch(0), node.ch(1).ch(0)), node.ch(0).ch(1)),
99 | });
100 | }
101 | if (node.ch(0).type === 'or' && node.ch(1).type === 'or'
102 | && node.ch(0).ch(1).type === 'not' && node.ch(1).ch(0).type === 'not'
103 | && node.ch(0).ch(0).toString() === node.ch(1).ch(0).ch(0).toString()
104 | && node.ch(0).ch(1).ch(0).toString() === node.ch(1).ch(1).toString()) {
105 | result.push({
106 | name: '从取假来描述双条件',
107 | result: new EqNode(node.ch(0).ch(0), node.ch(1).ch(1)),
108 | });
109 | }
110 | if (node.ch(0).type === 'imply' && node.ch(1).type === 'imply'
111 | && node.ch(0).ch(0).toString() === node.ch(1).ch(1).toString()
112 | && node.ch(0).ch(1).toString() === node.ch(1).ch(0).toString()) {
113 | result.push({
114 | name: '等价等值式',
115 | result: new EqNode(node.ch(0).ch(0), node.ch(0).ch(1)),
116 | });
117 | }
118 | if (node.ch(0).type === 'imply' && node.ch(1).type === 'imply' && node.ch(1).ch(1).type === 'not'
119 | && node.ch(0).ch(0).toString() === node.ch(1).ch(0).toString()
120 | && node.ch(0).ch(1).toString() === node.ch(1).ch(1).ch(0).toString()) {
121 | result.push({
122 | name: '归谬论',
123 | result: new NotNode(node.ch(0).ch(0)),
124 | });
125 | }
126 | break;
127 | case 'atom':
128 | break;
129 | case 'eq':
130 | if (node.ch(0).type === node.type) {
131 | result.push({
132 | name: '结合律',
133 | result: new EqNode(node.ch(0).ch(0), new EqNode(node.ch(0).ch(1), node.ch(1))),
134 | });
135 | }
136 | if (node.ch(1).type === node.type) {
137 | result.push({
138 | name: '结合律',
139 | result: new EqNode(new EqNode(node.ch(0), node.ch(1).ch(0)), node.ch(1).ch(1)),
140 | });
141 | }
142 | result.push({
143 | name: '交换律',
144 | result: new EqNode(node.ch(1), node.ch(0)),
145 | });
146 | if (node.ch(0).toString() === node.ch(1).toString()) {
147 | result.push({
148 | name: '等幂律',
149 | result: new TrueNode(),
150 | });
151 | }
152 | if (node.ch(0).type === 'true') {
153 | result.push({
154 | name: '同一律',
155 | result: node.ch(1),
156 | });
157 | }
158 | if (node.ch(0).type === 'false') {
159 | result.push({
160 | name: '同一律',
161 | result: new NotNode(node.ch(1)),
162 | });
163 | }
164 | if (node.ch(1).type === 'not' && node.ch(0).toString() === node.ch(1).ch(0).toString()) {
165 | result.push({
166 | name: '补余律',
167 | result: new FalseNode(),
168 | });
169 | }
170 | result.push({
171 | name: '从取真来描述双条件',
172 | result: new OrNode(
173 | new AndNode(node.ch(0), node.ch(1)),
174 | new AndNode(new NotNode(node.ch(0)), new NotNode(node.ch(1))),
175 | ),
176 | }, {
177 | name: '从取假来描述双条件',
178 | result: new AndNode(
179 | new OrNode(node.ch(0), new NotNode(node.ch(1))),
180 | new OrNode(new NotNode(node.ch(0)), node.ch(1)),
181 | ),
182 | }, {
183 | name: '等价等值式',
184 | result: new AndNode(
185 | new ImplyNode(node.ch(0), node.ch(1)),
186 | new ImplyNode(node.ch(1), node.ch(0)),
187 | ),
188 | }, {
189 | name: '等价否定等值式',
190 | result: new EqNode(new NotNode(node.ch(0)), new NotNode(node.ch(1))),
191 | });
192 | if (node.ch(0).type === 'not' && node.ch(1).type === 'not') {
193 | result.push({
194 | name: '等价否定等值式',
195 | result: new EqNode(node.ch(0).ch(0), node.ch(1).ch(0)),
196 | });
197 | }
198 | break;
199 | case 'false':
200 | result.push({
201 | name: 'F = ¬T',
202 | result: new NotNode(new TrueNode()),
203 | });
204 | break;
205 | case 'impliedby':
206 | result.push({
207 | name: '被蕴含的定义',
208 | result: new ImplyNode(node.ch(1), node.ch(0)),
209 | });
210 | break;
211 | case 'imply':
212 | if (node.ch(1).type === 'imply') {
213 | result.push({
214 | name: '分配律',
215 | result: new ImplyNode(
216 | new ImplyNode(node.ch(0), node.ch(1).ch(0)),
217 | new ImplyNode(node.ch(0), node.ch(1).ch(1)),
218 | ),
219 | });
220 | }
221 | if (node.ch(0).toString() === node.ch(1).toString()) {
222 | result.push({
223 | name: '等幂律',
224 | result: new TrueNode(),
225 | });
226 | }
227 | if (node.ch(0).type === 'true') {
228 | result.push({
229 | name: '同一律',
230 | result: node.ch(1),
231 | });
232 | }
233 | if (node.ch(1).type === 'false') {
234 | result.push({
235 | name: '同一律',
236 | result: new NotNode(node.ch(0)),
237 | });
238 | }
239 | if (node.ch(1).type === 'true') {
240 | result.push({
241 | name: '零律',
242 | result: node.ch(1),
243 | });
244 | }
245 | if (node.ch(0).type === 'false') {
246 | result.push({
247 | name: '零律',
248 | result: new TrueNode(),
249 | });
250 | }
251 | if (node.ch(1).type === 'not' && node.ch(0).toString() === node.ch(1).ch(0).toString()) {
252 | result.push({
253 | name: '补余律',
254 | result: node.ch(1),
255 | });
256 | }
257 | if (node.ch(0).type === 'not' && node.ch(1).toString() === node.ch(0).ch(0).toString()) {
258 | result.push({
259 | name: '补余律',
260 | result: node.ch(1),
261 | });
262 | }
263 | result.push({
264 | name: '蕴含等值式',
265 | result: new OrNode(new NotNode(node.ch(0)), node.ch(1)),
266 | }, {
267 | name: '假言易位',
268 | result: new ImplyNode(new NotNode(node.ch(1)), new NotNode(node.ch(0))),
269 | });
270 | if (node.ch(0).type === 'not' && node.ch(1).type === 'not') {
271 | result.push({
272 | name: '假言易位',
273 | result: new ImplyNode(node.ch(1).ch(0), node.ch(0).ch(0)),
274 | });
275 | }
276 | if (node.ch(1).type === 'imply') {
277 | result.push({
278 | name: '前提合取合并',
279 | result: new ImplyNode(new AndNode(node.ch(0), node.ch(1).ch(0)), node.ch(1).ch(1)),
280 | }, {
281 | name: '前提交换',
282 | result: new ImplyNode(node.ch(1).ch(0), new ImplyNode(node.ch(0), node.ch(1).ch(1))),
283 | });
284 | }
285 | if (node.ch(0).type === 'and') {
286 | result.push({
287 | name: '前提合取合并',
288 | result: new ImplyNode(node.ch(0).ch(0), new ImplyNode(node.ch(0).ch(1), node.ch(1))),
289 | });
290 | }
291 | if (node.ch(0).type === 'or') {
292 | result.push({
293 | name: '前提析取合并',
294 | result: new AndNode(
295 | new ImplyNode(node.ch(0).ch(0), node.ch(1)),
296 | new ImplyNode(node.ch(0).ch(1), node.ch(1)),
297 | ),
298 | });
299 | }
300 | break;
301 | case 'nand':
302 | result.push({
303 | name: '与非的定义',
304 | result: new NotNode(new AndNode(node.ch(0), node.ch(1))),
305 | });
306 | break;
307 | case 'nor':
308 | result.push({
309 | name: '或非的定义',
310 | result: new NotNode(new OrNode(node.ch(0), node.ch(1))),
311 | });
312 | break;
313 | case 'not':
314 | if (node.ch(0).type === 'not') {
315 | result.push({
316 | name: '双重否定律',
317 | result: node.ch(0).ch(0),
318 | });
319 | }
320 | if (node.ch(0).type === 'or') {
321 | result.push({
322 | name: '摩根律',
323 | result: new AndNode(new NotNode(node.ch(0).ch(0)), new NotNode(node.ch(0).ch(1))),
324 | });
325 | }
326 | if (node.ch(0).type === 'and') {
327 | result.push({
328 | name: '摩根律',
329 | result: new OrNode(new NotNode(node.ch(0).ch(0)), new NotNode(node.ch(0).ch(1))),
330 | });
331 | }
332 | if (node.ch(0).type === 'imply') {
333 | result.push({
334 | name: '摩根律',
335 | result: new AndNode(node.ch(0).ch(0), new NotNode(node.ch(0).ch(1))),
336 | });
337 | }
338 | if (node.ch(0).type === 'eq') {
339 | result.push({
340 | name: '摩根律',
341 | result: new OrNode(
342 | new AndNode(new NotNode(node.ch(0).ch(0)), node.ch(0).ch(1)),
343 | new AndNode(node.ch(0).ch(0), new NotNode(node.ch(0).ch(1))),
344 | ),
345 | });
346 | }
347 | if (node.ch(0).type === 'true') {
348 | result.push({
349 | name: '¬T = F',
350 | result: new FalseNode(),
351 | });
352 | }
353 | if (node.ch(0).type === 'false') {
354 | result.push({
355 | name: '¬F = T',
356 | result: new TrueNode(),
357 | });
358 | }
359 | break;
360 | case 'or':
361 | if (node.ch(0).type === node.type) {
362 | result.push({
363 | name: '结合律',
364 | result: new OrNode(node.ch(0).ch(0), new OrNode(node.ch(0).ch(1), node.ch(1))),
365 | });
366 | }
367 | if (node.ch(1).type === node.type) {
368 | result.push({
369 | name: '结合律',
370 | result: new OrNode(new OrNode(node.ch(0), node.ch(1).ch(0)), node.ch(1).ch(1)),
371 | });
372 | }
373 | result.push({
374 | name: '交换律',
375 | result: new OrNode(node.ch(1), node.ch(0)),
376 | });
377 | if (node.ch(1).type === 'and') {
378 | result.push({
379 | name: '分配律',
380 | result: new AndNode(
381 | new OrNode(node.ch(0), node.ch(1).ch(0)),
382 | new OrNode(node.ch(0), node.ch(1).ch(1)),
383 | ),
384 | });
385 | }
386 | if (node.ch(0).type === 'and' && node.ch(1).type === 'and' && node.ch(0).ch(0).toString() === node.ch(1).ch(0).toString()) {
387 | result.push({
388 | name: '分配律',
389 | result: new AndNode(node.ch(0).ch(0), new OrNode(node.ch(0).ch(1), node.ch(1).ch(1))),
390 | });
391 | }
392 | if (node.ch(0).toString() === node.ch(1).toString()) {
393 | result.push({
394 | name: '等幂律',
395 | result: node.ch(0),
396 | });
397 | }
398 | if (node.ch(1).type === 'and' && node.ch(0).toString() === node.ch(1).ch(0).toString()) {
399 | result.push({
400 | name: '吸收律',
401 | result: node.ch(0),
402 | });
403 | }
404 | if (node.ch(0).type === 'not' && node.ch(1).type === 'not') {
405 | result.push({
406 | name: '摩根律',
407 | result: new NotNode(new AndNode(node.ch(0).ch(0), node.ch(1).ch(0))),
408 | });
409 | }
410 | if (node.ch(0).type === 'and' && node.ch(1).type === 'and'
411 | && node.ch(0).ch(0).type === 'not' && node.ch(1).ch(1).type === 'not'
412 | && node.ch(0).ch(0).ch(0).toString() === node.ch(1).ch(0).toString()
413 | && node.ch(0).ch(1).toString() === node.ch(1).ch(1).ch(0).toString()) {
414 | result.push({
415 | name: '摩根律',
416 | result: new NotNode(new EqNode(node.ch(1).ch(0), node.ch(0).ch(1))),
417 | });
418 | }
419 | if (node.ch(1).type === 'false') {
420 | result.push({
421 | name: '同一律',
422 | result: node.ch(0),
423 | });
424 | }
425 | if (node.ch(1).type === 'true') {
426 | result.push({
427 | name: '零律',
428 | result: node.ch(1),
429 | });
430 | }
431 | if (node.ch(1).type === 'not' && node.ch(0).toString() === node.ch(1).ch(0).toString()) {
432 | result.push({
433 | name: '补余律',
434 | result: new TrueNode(),
435 | });
436 | }
437 | if (node.ch(0).type === 'not') {
438 | result.push({
439 | name: '蕴涵等值式',
440 | result: new ImplyNode(node.ch(0).ch(0), node.ch(1)),
441 | });
442 | }
443 | if (node.ch(0).type === 'and' && node.ch(1).type === 'and'
444 | && node.ch(1).ch(0).type === 'not' && node.ch(1).ch(1).type === 'not'
445 | && node.ch(0).ch(0).toString() === node.ch(1).ch(0).ch(0).toString()
446 | && node.ch(0).ch(1).toString() === node.ch(1).ch(1).ch(0).toString()) {
447 | result.push({
448 | name: '从取真来描述双条件',
449 | result: new EqNode(node.ch(0).ch(0), node.ch(0).ch(1)),
450 | });
451 | }
452 | break;
453 | case 'xor':
454 | result.push({
455 | name: '异或的定义',
456 | result: new NotNode(new EqNode(node.ch(0), node.ch(1))),
457 | });
458 | break;
459 | case 'true':
460 | result.push({
461 | name: 'T = ¬F',
462 | result: new NotNode(new FalseNode()),
463 | });
464 | break;
465 | default:
466 | exhaustiveCheck(node.type);
467 | }
468 |
469 | return result;
470 | }
471 |
--------------------------------------------------------------------------------
/src/core/getTable.ts:
--------------------------------------------------------------------------------
1 | import { h, RenderFunction } from 'vue';
2 | import { AstNode, AtomNode } from './AstNode';
3 | import Data from '~/types/data';
4 | import Step from '~/types/step';
5 |
6 | import SimplificationButton from '~/components/SimplificationButton.vue';
7 |
8 | interface Column {
9 | title: string | RenderFunction;
10 | key: string;
11 | align: 'center';
12 | children?: Column[];
13 | className?: string;
14 | }
15 |
16 | export type { Column };
17 |
18 | function getColumn(u: AstNode, root: AstNode, addStep: (step: Step) => void): Column {
19 | const { children, type } = u;
20 |
21 | const result: Column = {
22 | title: type !== 'atom' ? () => h(SimplificationButton, {
23 | node: u,
24 | root,
25 | addStep,
26 | }) : u.toString(),
27 | key: u.toString(),
28 | align: 'center',
29 | };
30 |
31 | const main = {
32 | title: u.operator,
33 | key: u.toString(),
34 | align: 'center',
35 | ...(u === root ? {
36 | className: 'truth-table-result',
37 | } : {}),
38 | } as const;
39 |
40 | if (children.length === 1) {
41 | result.children = [
42 | main,
43 | getColumn(children[0], root, addStep),
44 | ];
45 | } else if (children.length === 2) {
46 | result.children = [
47 | getColumn(children[0], root, addStep),
48 | main,
49 | getColumn(children[1], root, addStep),
50 | ];
51 | }
52 |
53 | return result;
54 | }
55 |
56 | export function getTable(
57 | root: AstNode,
58 | atomNodes: Map,
59 | addStep: (step: Step) => void,
60 | ) {
61 | const columns: Column[] = [];
62 |
63 | const atoms = Array.from(atomNodes.keys()).sort();
64 | atoms.forEach((atom) => {
65 | columns.push({
66 | title: atom,
67 | key: atom,
68 | align: 'center',
69 | });
70 | });
71 |
72 | columns.push(getColumn(root, root, addStep));
73 |
74 | const datas: Data[] = [];
75 | const truths: boolean[] = [];
76 |
77 | for (let mask = 0; mask < (1 << atoms.length); mask += 1) {
78 | const data: Data = { key: mask };
79 | for (let i = 0; i < atoms.length; i += 1) {
80 | data[atoms[i]] = (mask >> (atoms.length - 1 - i)) & 1;
81 | }
82 |
83 | truths.push(root.dfsTruth(data));
84 | datas.push(data);
85 | }
86 |
87 | return {
88 | atoms,
89 | columns,
90 | data: datas,
91 | truths,
92 | };
93 | }
94 |
--------------------------------------------------------------------------------
/src/core/transformExp.spec.ts:
--------------------------------------------------------------------------------
1 | import transformExp from './transformExp';
2 |
3 | describe('transformExp', () => {
4 | it('should transform brackets', () => {
5 | const result = transformExp('(([【{{))]】}}()【】}{[])({}');
6 | expect(result).toBe('(((((())))))()())(())(()');
7 | });
8 |
9 | it('should transform NOT', () => {
10 | const result = transformExp('!!¬!NoT~~~~¬nOt~~');
11 | expect(result).toBe('!!!!!!!!!!!!!');
12 | });
13 |
14 | it('should transform AND', () => {
15 | const result = transformExp('∧∧AnD&∧&&∧&&&&aND&');
16 | expect(result).toBe('&&&&&&&&&&&');
17 | });
18 |
19 | it('should transform NAND', () => {
20 | const result = transformExp('⊼↑NAND↑⊼⊼');
21 | expect(result).toBe('↑↑↑↑↑↑');
22 | });
23 |
24 | it('should distinguish NAND with AND', () => {
25 | const result = transformExp('AND nAND NAnD aND NANDAND ANDNAND');
26 | expect(result).toBe('&↑↑&NANDANDANDNAND');
27 | });
28 |
29 | it('should transform XOR', () => {
30 | const result = transformExp('⊕!=!=^⊻XoR…………!=^……⊻xOR');
31 | expect(result).toBe('^^^^^^^^^^^^^');
32 | });
33 |
34 | it('should transform OR', () => {
35 | const result = transformExp('oR∨||||||oROr||OR');
36 | expect(result).toBe('||||||oROr||');
37 | });
38 |
39 | it('should transform NOR', () => {
40 | const result = transformExp('↓NOr⊽↓⊽↓nor↓');
41 | expect(result).toBe('↓↓↓↓↓↓↓↓');
42 | });
43 |
44 | it('should distinguish OR, XOR and NOR', () => {
45 | const result = transformExp('or nor xor XNOR NXOR x oRn OR');
46 | expect(result).toBe('|↓^XNORNXORxoRn|');
47 | });
48 |
49 | it('should transform IMPLIES', () => {
50 | const result = transformExp('》>->→→-->implIes》IMPliES');
51 | expect(result).toBe('>>>>>->>>>');
52 | });
53 |
54 | it('should transform IMPLIEDBY', () => {
55 | const result = transformExp('《<<-←←<--implIedby《IMPlieDBY');
56 | expect(result).toBe('<<<<<<-<<<');
57 | });
58 |
59 | it('should transform EQ', () => {
60 | const result = transformExp('⟷<>=<->eq==⟷<->====↔Eq');
61 | expect(result).toBe('============');
62 | });
63 |
64 | it('should distinguish <> <- ->', () => {
65 | const result = transformExp('<< <-> >> <> --> <<--');
66 | expect(result).toBe('<<=>>=-><<-');
67 | });
68 |
69 | it('should have \\b', () => {
70 | const origin = 'NotAndNandXorOrNorImpliesImpliedbyEq';
71 | const result = transformExp(origin);
72 | expect(result).toBe(origin);
73 | });
74 |
75 | it('should remove spaces', () => {
76 | const result = transformExp(' \n\t\rAND\r\t\n\v ');
77 | expect(result).toBe('&');
78 | });
79 |
80 | it('should keep capital letters', () => {
81 | const result = transformExp('Q W E R T Y U I O P A S D F G H J K L Z X C V B N M');
82 | expect(result).toBe('QWERTYUIOPASDFGHJKLZXCVBNM');
83 | });
84 |
85 | it('should transform LaTeX', () => {
86 | const result = transformExp('\\uparrow\\downarrow\\land\\lor\\xor\\nand\\neg\\lor\\leftrightarrow\\lnot\\vee\\wedge\\gets\\to\\land\\nand\\nor\\rightarrow\\leftarrow\\land\\lor\\xor\\nand\\neg\\lor\\leftrightarrow\\gets\\to\\land\\nand\\nor\\rightarrow\\leftarrow');
87 | expect(result).toBe('↑↓&|^↑!|=!|&<>&↑↓><&|^↑!|=<>&↑↓><');
88 | });
89 |
90 | it('should have \\b for LaTeX', () => {
91 | const origin = '\\uparrowx\\downarrowx\\landx\\lorx\\xorx\\nandx\\negx\\lorx\\leftrightarrowx\\lnotx\\veex\\wedgex\\getsx\\tox\\landx\\nandd\\norr\\rightarroww\\leftarroww';
92 | const result = transformExp(origin);
93 | expect(result).toBe(origin);
94 | });
95 |
96 | it('should require "\\" for LaTeX', () => {
97 | const result = transformExp('uparrow downarrow land lor xor nand neg lor leftrightarrow lnot vee wedge gets to land nand nor rightarrow leftarrow');
98 | expect(result).toBe('uparrowdownarrowlandlor^↑neglorleftrightarrowlnotveewedgegetstoland↑↓rightarrowleftarrow');
99 | });
100 |
101 | it('should require lowercase for LaTeX', () => {
102 | const result = transformExp('\\Uparrow\\Downarrow\\Land\\Lor\\Xor\\Nand\\Neg\\Lor\\Leftrightarrow\\Lnot\\Vee\\Wedge\\Gets\\To\\Land\\Nand\\Nor\\Rightarrow\\Leftarrow');
103 | expect(result).toBe('\\Uparrow\\Downarrow\\Land\\Lor\\^\\↑\\Neg\\Lor\\Leftrightarrow\\Lnot\\Vee\\Wedge\\Gets\\To\\Land\\↑\\↓\\Rightarrow\\Leftarrow');
104 | });
105 |
106 | it('should accept true/false as T/F', () => {
107 | const result = transformExp('true false True fALsE truefalse');
108 | expect(result).toBe('TFTFtruefalse');
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/src/core/transformExp.ts:
--------------------------------------------------------------------------------
1 | export default function transformExp(exp: string): string {
2 | return exp
3 | .replace(/\\(land|wedge)\b/g, '&')
4 | .replace(/∧|&&|\bAND\b/gi, '&')
5 | .replace(/\\(nand|uparrow)\b/g, '↑')
6 | .replace(/⊼|\bNAND\b/gi, '↑')
7 | .replace(/\\xor\b/g, '^')
8 | .replace(/⊻|⊕|!=|……|\bXOR\b/gi, '^')
9 | .replace(/\\(nor|downarrow)\b/g, '↓')
10 | .replace(/⊽|\bNOR\b/gi, '↓')
11 | .replace(/\\(lor|vee)\b/g, '|')
12 | .replace(/∨|||\|\||\bOR\b/gi, '|')
13 | .replace(/\\leftrightarrow\b/g, '=')
14 | .replace(/⟷|↔|==|<->|<>|\bEQ\b/gi, '=')
15 | .replace(/\\(to|rightarrow)\b/g, '>')
16 | .replace(/→|》|->|\bIMPLIES\b/gi, '>')
17 | .replace(/\\(gets|leftarrow)\b/g, '<')
18 | .replace(/←|《|<-|\bIMPLIEDBY\b/gi, '<')
19 | .replace(/\\(neg|lnot)\b/g, '!')
20 | .replace(/¬|~|!|~|\bNOT\b/gi, '!')
21 | .replace(/(|\[|【|\{|{/g, '(')
22 | .replace(/)|\]|】|\}|}/g, ')')
23 | .replace(/\btrue\b/gi, 'T')
24 | .replace(/\bfalse\b/gi, 'F')
25 | .replace(/\s/g, '');
26 | }
27 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from '~/App.vue';
3 |
4 | createApp(App).mount('#app');
5 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue';
3 |
4 | const component: DefineComponent, Record, unknown>;
5 | export default component;
6 | }
7 |
--------------------------------------------------------------------------------
/src/types/WangHaoTooLongError.ts:
--------------------------------------------------------------------------------
1 | export default class WangHaoTooLongError extends Error { }
2 |
--------------------------------------------------------------------------------
/src/types/data.ts:
--------------------------------------------------------------------------------
1 | export default interface Data {
2 | [index: string]: number;
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/equivalent.ts:
--------------------------------------------------------------------------------
1 | import type AstNode from '~/core/AstNode/base';
2 |
3 | export default interface Equivalent {
4 | name: string;
5 | result: AstNode;
6 | }
7 |
--------------------------------------------------------------------------------
/src/types/step.ts:
--------------------------------------------------------------------------------
1 | export default interface Step {
2 | exp: string;
3 | rule: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "module": "es2015",
5 | "strict": true,
6 | "importHelpers": true,
7 | "moduleResolution": "node",
8 | "experimentalDecorators": true,
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "sourceMap": true,
15 | "baseUrl": ".",
16 | "types": [
17 | "node",
18 | "vite/client",
19 | "jest"
20 | ],
21 | "paths": {
22 | "~/*": [
23 | "src/*"
24 | ]
25 | },
26 | "lib": [
27 | "es2015",
28 | "dom",
29 | "dom.iterable",
30 | "scripthost"
31 | ]
32 | },
33 | "include": [
34 | "src/**/*.ts",
35 | "src/**/*.vue"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import vue from '@vitejs/plugin-vue';
3 | import { resolve } from 'path';
4 |
5 | export default defineConfig({
6 | plugins: [
7 | vue(),
8 | ],
9 | resolve: {
10 | alias: {
11 | '~': resolve(__dirname, 'src'),
12 | },
13 | },
14 | base: '',
15 | build: {
16 | target: 'es2015',
17 | minify: 'terser',
18 | },
19 | });
20 |
--------------------------------------------------------------------------------