diff --git a/TODO b/TODO
index 7ee5542..72c4d95 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,5 @@
Add screen (VRAM?)
Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying)
Draw lines between registers and memory when used
-Explain mode to explain what instructions are doing
-Window showing ISA
+
Save binary button
\ No newline at end of file
diff --git a/index.html b/index.html
index 8850f76..9fb742b 100644
--- a/index.html
+++ b/index.html
@@ -4,30 +4,27 @@
-
VIRTUAL 8-BIT COMPUTER
-
-
diff --git a/package-lock.json b/package-lock.json
index c9abae1..2b3b42f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,10 +7,14 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
+ "css-loader": "^6.10.0",
"eslint": "^8.44.0",
+ "sass": "^1.71.0",
+ "sass-loader": "^14.1.0",
+ "style-loader": "^3.3.4",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
- "webpack": "^5.88.2",
+ "webpack": "^5.90.2",
"webpack-cli": "^5.1.4"
}
},
@@ -782,6 +786,19 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -803,6 +820,15 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -907,6 +933,42 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
@@ -980,6 +1042,53 @@
"node": ">= 8"
}
},
+ "node_modules/css-loader": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz",
+ "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==",
+ "dev": true,
+ "dependencies": {
+ "icss-utils": "^5.1.0",
+ "postcss": "^8.4.33",
+ "postcss-modules-extract-imports": "^3.0.0",
+ "postcss-modules-local-by-default": "^4.0.4",
+ "postcss-modules-scope": "^3.1.1",
+ "postcss-modules-values": "^4.0.0",
+ "postcss-value-parser": "^4.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1397,6 +1506,20 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -1534,6 +1657,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/icss-utils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
+ "dev": true,
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -1543,6 +1678,12 @@
"node": ">= 4"
}
},
+ "node_modules/immutable": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
+ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
+ "dev": true
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -1612,6 +1753,18 @@
"node": ">=10.13.0"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@@ -1898,6 +2051,24 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -1916,6 +2087,15 @@
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2117,6 +2297,112 @@
"node": ">=8"
}
},
+ "node_modules/postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-modules-extract-imports": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
+ "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
+ "dev": true,
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-local-by-default": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz",
+ "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==",
+ "dev": true,
+ "dependencies": {
+ "icss-utils": "^5.0.0",
+ "postcss-selector-parser": "^6.0.2",
+ "postcss-value-parser": "^4.1.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-scope": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz",
+ "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==",
+ "dev": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.4"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-values": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+ "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
+ "dev": true,
+ "dependencies": {
+ "icss-utils": "^5.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.15",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
+ "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2164,6 +2450,18 @@
"safe-buffer": "^5.1.0"
}
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
@@ -2291,6 +2589,63 @@
}
]
},
+ "node_modules/sass": {
+ "version": "1.71.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz",
+ "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-loader": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.0.tgz",
+ "integrity": "sha512-LS2mLeFWA+orYxHNu+O18Xe4jR0kyamNOOUsE3NyBP4DvIL+8stHpNX0arYTItdPe80kluIiJ7Wfe/9iHSRO0Q==",
+ "dev": true,
+ "dependencies": {
+ "neo-async": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
+ "sass": "^1.3.0",
+ "sass-embedded": "*",
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "node-sass": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
"node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -2384,6 +2739,15 @@
"node": ">= 8"
}
},
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -2427,6 +2791,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/style-loader": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
+ "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.0.0"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -2644,6 +3024,12 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
@@ -2658,9 +3044,9 @@
}
},
"node_modules/webpack": {
- "version": "5.90.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
- "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
+ "version": "5.90.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.2.tgz",
+ "integrity": "sha512-ziXu8ABGr0InCMEYFnHrYweinHK2PWrMqnwdHk2oK3rRhv/1B+2FnfwYv5oD+RrknK/Pp/Hmyvu+eAsaMYhzCw==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
diff --git a/package.json b/package.json
index 7a2c542..515d454 100644
--- a/package.json
+++ b/package.json
@@ -3,14 +3,18 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
+ "css-loader": "^6.10.0",
"eslint": "^8.44.0",
+ "sass": "^1.71.0",
+ "sass-loader": "^14.1.0",
+ "style-loader": "^3.3.4",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
- "webpack": "^5.88.2",
+ "webpack": "^5.90.2",
"webpack-cli": "^5.1.4"
},
"scripts": {
"build": "webpack --mode=production",
"watch": "webpack --mode=development --watch"
}
-}
\ No newline at end of file
+}
diff --git a/src/computer.ts b/src/computer.ts
index a202dec..4c07eaf 100644
--- a/src/computer.ts
+++ b/src/computer.ts
@@ -1,7 +1,7 @@
-import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
+import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { byte_array_to_js_source, format_hex, u8 } from "./etc";
import { EventHandler } from "./eventHandler";
-import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet";
+import { Instruction, ISA } from "./instructionSet";
export type TempInstrState = {
pos: u8;
@@ -9,16 +9,11 @@ export type TempInstrState = {
instr: Instruction;
params: Uint8Array;
};
-export type ComputerState = {
- memory: Uint8Array;
- program_counter: u8;
- registers: Uint8Array;
- current_instruction: TempInstrState | null;
-};
export class Computer {
private memory = new Uint8Array(256);
private registers = new Uint8Array(8);
+ private call_stack: Array
= [];
private program_counter: u8 = 0;
private current_instr: TempInstrState | null = null;
events: CpuEventHandler = new EventHandler() as CpuEventHandler;
@@ -36,13 +31,13 @@ export class Computer {
if (this.current_instr === null) {
const parsed_instruction = ISA.getInstruction(current_byte);
if (parsed_instruction === null) {
- this.events.dispatch(CpuEvent.MemoryByteParsed, {
- type: MemoryCellType.InvalidInstruction,
+ this.events.dispatch(CpuEvent.InvalidParsed, {
pos: this.program_counter,
code: current_byte,
});
console.log(`Invalid instruction: ${format_hex(current_byte)}`);
this.step_forward();
+ this.events.dispatch(CpuEvent.ClockCycle, null);
return;
}
this.current_instr = {
@@ -51,8 +46,7 @@ export class Computer {
params_found: 0,
params: new Uint8Array(parsed_instruction.params.length),
};
- this.events.dispatch(CpuEvent.MemoryByteParsed, {
- type: MemoryCellType.Instruction,
+ this.events.dispatch(CpuEvent.InstructionParsed, {
pos: this.program_counter,
instr: parsed_instruction,
code: current_byte,
@@ -61,32 +55,23 @@ export class Computer {
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
this.step_forward();
+ this.events.dispatch(CpuEvent.ClockCycle, null);
return;
}
if (this.current_instr.params.length !== this.current_instr.params_found) {
- let a;
const b = this.current_instr.instr.params[this.current_instr.params_found];
- if (b instanceof ConstParam) {
- a = MemoryCellType.Constant;
- } else if (b instanceof RegisParam) {
- a = MemoryCellType.Register;
- } else if (b instanceof MemorParam) {
- a = MemoryCellType.Memory;
- }
- if (a === undefined) {
- throw new Error("Shouldn't");
- }
- this.events.dispatch(CpuEvent.MemoryByteParsed, {
- type: a,
+
+ this.events.dispatch(CpuEvent.ParameterParsed, {
+ param: b,
pos: this.program_counter,
code: current_byte,
- param: b,
});
this.current_instr.params[this.current_instr.params_found] = current_byte;
this.current_instr.params_found += 1;
if (this.current_instr.params.length !== this.current_instr.params_found) {
this.step_forward();
+ this.events.dispatch(CpuEvent.ClockCycle, null);
return;
}
}
@@ -104,6 +89,7 @@ export class Computer {
if (execution_post_action_state.should_step) {
this.step_forward();
}
+ this.events.dispatch(CpuEvent.ClockCycle, null);
}
getMemory(address: u8): u8 {
@@ -130,18 +116,30 @@ export class Computer {
this.program_counter = new_value;
}
+ pushCallStack(address: u8): boolean {
+ if (this.call_stack.length >= 8) return false;
+ this.call_stack.push(address);
+ return true;
+ }
+
+ popCallStack(): u8 | null {
+ return this.call_stack.pop() ?? null;
+ }
+
reset(): void {
+ this.events.dispatch(CpuEvent.Reset, null);
this.memory = new Uint8Array(256);
this.registers = new Uint8Array(8);
+ this.call_stack = [];
this.current_instr = null;
this.program_counter = 0;
- this.events.dispatch(CpuEvent.Reset, null);
}
init_events(ui: UiEventHandler): void {
- ui.listen(UiEvent.RequestCpuCycle, () => {
- this.cycle();
+ ui.listen(UiEvent.RequestCpuCycle, (n) => {
+ for (let i = 0; i < n; i++) this.cycle();
});
+ ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
}
load_memory(program: Array): void {
@@ -149,9 +147,8 @@ export class Computer {
const max_loop = Math.min(this.memory.length, program.length);
for (let i = 0; i < max_loop; i++) {
// Don't fire event if no change is made
- if (this.memory[i] === program[i]) {
- continue;
- }
+ if (this.memory[i] === program[i]) continue;
+
this.memory[i] = program[i];
this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] });
}
diff --git a/src/etc.ts b/src/etc.ts
index 924d35e..47ae339 100644
--- a/src/etc.ts
+++ b/src/etc.ts
@@ -1,4 +1,7 @@
+import { UiEventHandler } from "./events";
+
// The u8 type represents an unsigned 8bit integer: byte. It does not add any safety, other than as a hint to the programmer.
+// export type u8 = 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255;
export type u8 = number;
// Jquery lite
diff --git a/src/eventHandler.ts b/src/eventHandler.ts
index 68972b4..42eb749 100644
--- a/src/eventHandler.ts
+++ b/src/eventHandler.ts
@@ -27,7 +27,7 @@ export class EventHandler {
const event = new Event(identifier);
this.events.push(event);
}
- dispatch(identifier: T, event_data: unknown): void {
+ dispatch(identifier: T, event_data?: unknown): void {
const event = this.events.find((e) => e.identifier === identifier);
if (event === undefined) {
throw new Error("Event not found");
diff --git a/src/events.ts b/src/events.ts
index 0fa883a..f2d1251 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -6,18 +6,14 @@ export enum CpuEvent {
MemoryChanged,
RegisterChanged,
ProgramCounterChanged,
- MemoryByteParsed,
+ InstructionParsed,
+ ParameterParsed,
+ InvalidParsed,
InstructionExecuted,
+ ClockCycle,
Print,
Reset,
-}
-
-export enum MemoryCellType {
- Instruction,
- InvalidInstruction,
- Register,
- Memory,
- Constant,
+ Halt,
}
// Handily explained in https://www.cgjennings.ca/articles/typescript-events/
@@ -25,8 +21,12 @@ interface CpuEventMap {
[CpuEvent.MemoryChanged]: { address: u8; value: u8 };
[CpuEvent.RegisterChanged]: { register_no: u8; value: u8 };
[CpuEvent.ProgramCounterChanged]: { counter: u8 };
+ [CpuEvent.Halt]: null;
[CpuEvent.Reset]: null;
- [CpuEvent.MemoryByteParsed]: { type: MemoryCellType; pos: u8; code: u8; param?: ParameterType; instr?: Instruction };
+ [CpuEvent.ClockCycle]: null;
+ [CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction };
+ [CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
+ [CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
[CpuEvent.InstructionExecuted]: { instr: Instruction };
[CpuEvent.Print]: string;
}
@@ -38,10 +38,12 @@ export interface CpuEventHandler extends EventHandler {
export enum UiEvent {
RequestCpuCycle,
+ RequestMemoryChange,
}
interface UiEventMap {
- [UiEvent.RequestCpuCycle]: null;
+ [UiEvent.RequestCpuCycle]: number;
+ [UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
}
export interface UiEventHandler extends EventHandler {
diff --git a/src/index.ts b/src/index.ts
index e97a127..4859439 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,9 +4,28 @@ import { ISA } from "./instructionSet";
import { generate_isa } from "./isaGenerator";
import { UI } from "./ui";
+import "./style.scss";
+
function main(): void {
+ // const program = [
+ // 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
+ // 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
+ // ];
+
const program = [
- 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
+ 0x01, 0x00, 0x01, 0x00, 0xa0, 0x10, 0xff, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xa1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -22,28 +41,25 @@ function main(): void {
0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
];
- const container = $("container");
- if (container === null) {
- throw new Error("no");
- }
const computer = new Computer();
- const ui = new UI(container);
+ const ui = new UI();
ui.init_events(computer.events);
computer.load_memory(program);
computer.init_events(ui.events);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window).comp = computer;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (window).ui = ui;
$("ISA").textContent = generate_isa(ISA);
- // eslint-disable-next-line prefer-arrow-callback
- $("binary_upload").addEventListener("change", function (e) {
+ $("binary_upload").addEventListener("change", (e) => {
if (e.target === null) {
return;
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-destructuring
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const file: File = (e.target).files[0];
const reader = new FileReader();
console.log(file);
diff --git a/src/instructionSet.ts b/src/instructionSet.ts
index dafec60..52726da 100644
--- a/src/instructionSet.ts
+++ b/src/instructionSet.ts
@@ -1,16 +1,36 @@
import { CpuEvent, CpuEventHandler } from "./events";
import { format_hex, u8 } from "./etc";
-export class ParameterType {
+export enum ParamType {
+ Const,
+ Register,
+ Memory,
+}
+
+export abstract class ParameterType {
readonly desc: string;
- constructor(description: string) {
+ readonly type: ParamType;
+ constructor(description: string, p_type: ParamType) {
this.desc = description;
+ this.type = p_type;
}
}
-export class ConstParam extends ParameterType {}
-export class RegisParam extends ParameterType {}
-export class MemorParam extends ParameterType {}
+class ConstParam extends ParameterType {
+ constructor(d: string) {
+ super(d, ParamType.Const);
+ }
+}
+class RegisParam extends ParameterType {
+ constructor(d: string) {
+ super(d, ParamType.Register);
+ }
+}
+class MemorParam extends ParameterType {
+ constructor(d: string) {
+ super(d, ParamType.Memory);
+ }
+}
interface GenericComputer {
getMemory: (address: u8) => u8;
@@ -19,6 +39,8 @@ interface GenericComputer {
getProgramCounter: () => u8;
getRegister: (number: u8) => u8;
setRegister: (number: u8, value: u8) => void;
+ pushCallStack: (address: u8) => boolean;
+ popCallStack: () => u8 | null;
}
interface AfterExecutionComputerAction {
@@ -31,7 +53,11 @@ export interface Instruction {
readonly name: string;
readonly desc: string;
readonly params: Array;
- execute: (computer_reference: GenericComputer, parameters: Uint8Array, a: AfterExecutionComputerAction) => void;
+ readonly execute: (
+ computer_reference: GenericComputer,
+ parameters: Uint8Array,
+ a: AfterExecutionComputerAction
+ ) => void;
}
export class InstructionSet {
@@ -55,6 +81,8 @@ export class InstructionSet {
export const ISA = new InstructionSet();
+// The definitions for actual instructions.
+
ISA.insertInstruction(0x00, {
name: "NoOp",
desc: "No operation; do nothing",
@@ -86,7 +114,7 @@ ISA.insertInstruction(0x20, {
ISA.insertInstruction(0x21, {
name: "SaveToMemory",
desc: "Writes the byte in register (P1) to the memory cell (P2)",
- params: [new RegisParam(""), new MemorParam("")],
+ params: [new RegisParam("Write the byte in this register"), new MemorParam("To this memory address")],
execute(c, p) {
const [register_no, mem_address] = p;
c.setMemory(mem_address, c.getRegister(register_no));
@@ -115,6 +143,7 @@ ISA.insertInstruction(0x11, {
}
},
});
+
ISA.insertInstruction(0x30, {
name: "Increment",
desc: "Increments the value within register (P1) by 1",
@@ -122,7 +151,8 @@ ISA.insertInstruction(0x30, {
execute(c, p) {
const register_no = p[0];
const current_value = c.getRegister(register_no);
- c.setRegister(register_no, current_value + 1);
+ const new_value = (current_value + 1) % 256;
+ c.setRegister(register_no, new_value);
},
});
@@ -133,14 +163,18 @@ ISA.insertInstruction(0x31, {
execute(c, p) {
const register_no = p[0];
const current_value = c.getRegister(register_no);
- c.setRegister(register_no, current_value - 1);
+ let new_value = current_value - 1;
+ if (new_value === -1) {
+ new_value = 255;
+ }
+ c.setRegister(register_no, new_value);
},
});
ISA.insertInstruction(0x40, {
name: "Add",
desc: "Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)",
- params: [new RegisParam(""), new RegisParam("")],
+ params: [new RegisParam("set this register to"), new RegisParam("it's sum with the value in this register")],
execute(c, p) {
const [register_no_1, register_no_2] = p;
const new_value = (c.getRegister(register_no_1) + c.getRegister(register_no_2)) % 256;
@@ -175,3 +209,81 @@ ISA.insertInstruction(0xfe, {
a.dispatch(CpuEvent.Print, char);
},
});
+
+ISA.insertInstruction(0x48, {
+ name: "Bitwise And",
+ desc: "Takes each bit in register (P1) and compares to the respective bit in register (P2). Each bit in register (P1) is set to 1 if the respective bit in both registers are 1",
+ params: [new RegisParam(""), new RegisParam("")],
+ execute(c, p) {
+ const [register_no_1, register_no_2] = p;
+ const new_value = c.getRegister(register_no_1) & c.getMemory(register_no_2);
+ c.setRegister(register_no_1, new_value);
+ },
+});
+
+ISA.insertInstruction(0xff, {
+ name: "Print",
+ desc: "Prints the byte in register (P1) to console as base 10",
+ params: [new RegisParam("Register to print from")],
+ execute(c, p, a) {
+ const register_no = p[0];
+ const byte = c.getRegister(register_no);
+
+ a.dispatch(CpuEvent.Print, byte.toString(10));
+ },
+});
+
+ISA.insertInstruction(0xfd, {
+ name: "Print 16 bit",
+ desc: "Prints the byte in register (P1) as the upper half and the byte in register (P2) as the lower half of a 16 bit number. Formats to decimal",
+ params: [new RegisParam("Upper 8 bits of number"), new RegisParam("Lower 8 bits of number")],
+ execute(c, p, a) {
+ const [upper_register_no, lower_register_no] = p;
+ const upper = c.getRegister(upper_register_no);
+ const lower = c.getRegister(lower_register_no);
+ const sum = upper * 16 * 16 + lower;
+
+ a.dispatch(CpuEvent.Print, sum.toString(10));
+ },
+});
+
+ISA.insertInstruction(0x66, {
+ name: "Halt and Catch Fire",
+ desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
+ params: [],
+ execute(c, p, a) {
+ a.dispatch(CpuEvent.Halt, null);
+ },
+});
+
+ISA.insertInstruction(0xa0, {
+ name: "Call",
+ desc: "",
+ params: [new ConstParam("the subroutine at this memory address")],
+ execute(c, p, a) {
+ const current_address = c.getProgramCounter();
+ const success = c.pushCallStack(current_address);
+ // TODO Handle success/failure
+
+ const new_address = p[0];
+ c.setProgramCounter(new_address);
+
+ a.noStep();
+ },
+});
+
+ISA.insertInstruction(0xa1, {
+ name: "Return",
+ desc: "",
+ params: [],
+ execute(c, p, a) {
+ const new_address = c.popCallStack();
+ if (new_address === null) {
+ throw new Error("TODO handle this");
+ }
+
+ c.setProgramCounter(new_address + 1);
+
+ a.noStep();
+ },
+});
diff --git a/src/isaGenerator.ts b/src/isaGenerator.ts
index 2b4a088..5a9d630 100644
--- a/src/isaGenerator.ts
+++ b/src/isaGenerator.ts
@@ -3,15 +3,14 @@ import { Instruction, InstructionSet } from "./instructionSet";
export function generate_isa(iset: InstructionSet): string {
const instructions: Array<[u8, Instruction]> = [];
- for (const kv of iset.instructions.entries()) {
- instructions.push(kv);
- }
+
+ for (const kv of iset.instructions.entries()) instructions.push(kv);
+
let output_string = "INSTRUCTIONS\n";
- let max_instr_name_len = 0;
- for (const instruction of instructions) {
- const short_description = instruction[1].name;
- max_instr_name_len = Math.max(max_instr_name_len, short_description.length);
- }
+
+ const max_instr_name_len = instructions.map((i) => i[1].name.length).reduce((acc, p) => Math.max(p, acc), 0);
+ instructions.sort((a, b) => a[0] - b[0]);
+
for (const instruction of instructions) {
const hex_code = format_hex(instruction[0]);
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
diff --git a/src/style.scss b/src/style.scss
new file mode 100644
index 0000000..ea3f147
--- /dev/null
+++ b/src/style.scss
@@ -0,0 +1,192 @@
+* {
+ box-sizing: border-box;
+}
+
+pre {
+ font-size: 0.5em;
+}
+
+:root {
+ --Border: Yellow;
+ --mem-instruction: greenyellow;
+ --mem-register: Purple;
+ --mem-constant: lightgray;
+ --mem-memory: lightgray;
+ --mem-invalid: red;
+}
+
+body {
+ color: gray;
+ background-color: black;
+ font-size: 2.5em;
+ font-family: monospace;
+}
+
+#labelcontainer {
+ column-gap: 18px;
+ font-size: 0.85em;
+ display: flex;
+ align-items: center;
+ user-select: none;
+ grid-area: regmemlabel;
+}
+
+#main {
+ justify-content: center;
+ display: grid;
+ grid-template-columns: min-content max-content max-content 10px 500px;
+ grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
+ grid-template-areas:
+ "cycles registers regmemlabel . explainer "
+ "title . . . explainer "
+ "title . . . ."
+ "title . . . printout "
+ "title . . . printout "
+ ". buttons buttons buttons buttons";
+ #memory {
+ grid-column: 2/4;
+ grid-row: 2/6;
+ }
+ #cycles {
+ grid-area: cycles;
+ text-align: left;
+ align-self: center;
+ font-size: 0.48em;
+ }
+ #title {
+ grid-area: title;
+ writing-mode: vertical-lr;
+ text-align: left;
+ user-select: none;
+ transform: scale(-1, -1);
+ }
+}
+
+#printout {
+ border: 4px dashed yellow;
+ grid-area: printout;
+ padding: 10px;
+ word-wrap: break-word;
+ word-break: break-all;
+ max-height: 200px;
+ overflow-x: scroll;
+}
+
+#instruction_explainer {
+ grid-area: explainer;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ border: 5px dashed yellow;
+ #expl_box {
+ padding-inline: 20px;
+ padding-block-start: 10px;
+ }
+
+ #expl_text {
+ font-size: 0.6em;
+ text-align: center;
+ vertical-align: center;
+ }
+
+ #expl_icon {
+ margin-inline-end: 0.5em;
+ font-size: 30px;
+ padding: 5px;
+ color: var(--color);
+ outline: 3px dashed var(--color);
+ }
+}
+
+.invalid {
+ --color: var(--mem-invalid);
+}
+.constant {
+ --color: var(--mem-constant);
+}
+.register {
+ --color: var(--mem-register);
+}
+.memory {
+ --color: var(--mem-memory);
+}
+.instruction {
+ --color: var(--mem-instruction);
+}
+
+#memory {
+ grid-area: memory;
+ display: grid;
+ grid-template-columns: repeat(16, min-content);
+ gap: 5px;
+ padding: 10px;
+ border: 5px solid yellow;
+ div {
+ text-align: center;
+ color: var(--color);
+ }
+ .program_counter {
+ outline: 3px solid orange;
+ }
+ .instruction_argument {
+ outline: 3px dashed purple;
+ }
+ .current_instruction {
+ outline: 3px dashed greenyellow;
+ }
+
+ .invalid {
+ &::after {
+ user-select: none;
+ float: right;
+ position: relative;
+ right: 0.5em;
+ width: 0px;
+ font-size: 0.5em;
+ content: "!";
+ }
+ }
+}
+
+#registers {
+ grid-area: registers;
+ border: 5px solid yellow;
+ border-bottom: none;
+
+ grid-template-columns: repeat(8, min-content);
+ max-width: fit-content;
+ display: grid;
+ column-gap: 5px;
+ color: lightgray;
+ padding: 10px;
+
+ div {
+ max-height: min-content;
+ // margin-block: auto;
+ }
+}
+
+button,
+label.button {
+ border: 4px solid yellow;
+ color: gray;
+ margin: 10px;
+ margin-inline: 0px;
+ padding: 10px;
+ font-size: 0.8em;
+ font-family: monospace;
+ background-color: transparent;
+ cursor: pointer;
+ user-select: none;
+}
+
+button:hover,
+label.button:hover {
+ color: white;
+}
+
+#controls_bar {
+ grid-area: buttons;
+ display: flex;
+ gap: 10px;
+}
diff --git a/src/ui.ts b/src/ui.ts
index ef7ad6e..4709e19 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -1,41 +1,40 @@
-import { ComputerState } from "./computer";
-import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
+import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { $, el, format_hex, u8 } from "./etc";
-import { Instruction, ParameterType } from "./instructionSet";
import { EventHandler } from "./eventHandler";
+import { InstructionExplainer } from "./ui/instructionExplainer";
+import { MemoryView } from "./ui/memoryView";
+import { ParamType } from "./instructionSet";
+import { frequencyIndicator } from "./ui/frequencyIndicator";
+// Certainly the messiest portion of this program
+// Needs to be broken into components
+
+let delay = 100;
export class UI {
- container: HTMLElement;
- program_memory: HTMLElement;
- program_memory_cells: Array = [];
registers: HTMLElement;
printout: HTMLElement;
- instruction_explainer: HTMLElement;
register_cells: Array = [];
- instruction_parsing_addresses: Array = [];
- program_counter: u8 = 0;
auto_running: boolean;
events: UiEventHandler = new EventHandler() as UiEventHandler;
- constructor(parent: HTMLElement) {
+ frequencyIndicator: frequencyIndicator;
+ memory: MemoryView;
+ instruction_explainer: InstructionExplainer;
+
+ constructor() {
for (const [, e_type] of Object.entries(UiEvent)) {
this.events.register_event(e_type as UiEvent);
}
this.events.seal();
- this.container = parent;
+
+ this.memory = new MemoryView($("memory"));
+ this.frequencyIndicator = new frequencyIndicator($("cycles"));
+
+ this.instruction_explainer = new InstructionExplainer($("instruction_explainer"));
+
this.printout = $("printout");
- this.instruction_explainer = $("instruction_explainer");
- const program_mem = $("memory");
- for (let i = 0; i < 256; i++) {
- const mem_cell = el("div", `p_${i}`);
- mem_cell.textContent = "00";
- program_mem.appendChild(mem_cell);
- this.program_memory_cells.push(mem_cell);
- }
- this.program_memory_cells[0].classList.add("div", "program_counter");
- this.program_memory = program_mem;
const registers = $("registers");
for (let i = 0; i < 8; i++) {
@@ -66,21 +65,24 @@ export class UI {
this.stop_auto();
}
- this.events.dispatch(UiEvent.RequestCpuCycle, null);
+ this.events.dispatch(UiEvent.RequestCpuCycle, 1);
+ });
+
+ $("delay_range").addEventListener("input", (e) => {
+ delay = parseInt((e.target as HTMLInputElement).value, 10);
+ // console.log(delay);
});
}
init_events(cpu_events: CpuEventHandler): void {
cpu_events.listen(CpuEvent.MemoryChanged, ({ address, value }) => {
- this.program_memory_cells[address].textContent = format_hex(value);
+ this.memory.set_cell_value(address, value);
});
cpu_events.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
this.register_cells[register_no].textContent = format_hex(value);
});
cpu_events.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => {
- this.program_memory_cells[this.program_counter].classList.remove("program_counter");
- this.program_memory_cells[counter].classList.add("program_counter");
- this.program_counter = counter;
+ this.memory.set_program_counter(counter);
});
cpu_events.listen(CpuEvent.Print, (char) => {
this.printout.textContent = (this.printout.textContent ?? "") + char;
@@ -89,78 +91,51 @@ export class UI {
this.reset();
});
- const map: Map = new Map();
+ this.frequencyIndicator.init_cpu_events(cpu_events);
+ this.memory.init_cpu_events(cpu_events);
+ this.instruction_explainer.init_cpu_events(cpu_events);
- map.set(MemoryCellType.Constant, "constant");
- map.set(MemoryCellType.Instruction, "instruction");
- map.set(MemoryCellType.InvalidInstruction, "invalid_instruction");
- map.set(MemoryCellType.Memory, "memory");
- map.set(MemoryCellType.Register, "register");
- cpu_events.listen(CpuEvent.MemoryByteParsed, (e) => {
- const { type, pos, code } = e;
- const css_class = map.get(type);
- if (css_class === undefined) {
- throw new Error("Something went wrong");
+ cpu_events.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => {
+ this.memory.add_cell_class(pos, "instruction_argument");
+ this.instruction_explainer.add_param(param, pos, code);
+ const t = param.type;
+ this.memory.remove_cell_class(pos, "constant", "register", "memory", "instruction", "invalid");
+ let name: string = "";
+ if (t === ParamType.Const) {
+ name = "constant";
+ } else if (t === ParamType.Memory) {
+ name = "memory";
+ } else if (t === ParamType.Register) {
+ name = "register";
+ } else {
+ throw new Error("unreachable");
}
- for (const other_class of map.values()) {
- if (other_class === css_class) continue;
- this.program_memory_cells[pos].classList.remove(other_class);
- }
- if (type === MemoryCellType.Instruction) {
- while (this.instruction_parsing_addresses.length > 0) {
- const num = this.instruction_parsing_addresses.pop();
- if (num === undefined) {
- throw new Error("Shouldn't happen");
- }
- this.program_memory_cells[num].classList.remove("instruction_argument");
- this.program_memory_cells[num].classList.remove("current_instruction");
- }
- this.instruction_explainer.innerHTML = "";
- const { instr } = e as { instr: Instruction };
- this.program_memory_cells[pos].classList.add("current_instruction");
- this.instruction_parsing_addresses.push(pos);
- const instr_box = el("div", "expl_box");
- const instr_icon = el("span", "expl_icon");
- instr_icon.classList.add(css_class);
- instr_icon.setAttribute("title", css_class.toUpperCase());
- instr_icon.textContent = format_hex(code);
- const instr_box_text = el("span", "expl_text");
- instr_box_text.textContent = `${instr.name}`;
- instr_box.appendChild(instr_icon);
- instr_box.appendChild(instr_box_text);
- this.instruction_explainer.appendChild(instr_box);
- } else if (type !== MemoryCellType.InvalidInstruction) {
- const { param } = e as { param: ParameterType };
- this.program_memory_cells[pos].classList.add("instruction_argument");
- this.instruction_parsing_addresses.push(pos);
- const instr_box = el("div", "expl_box");
- const instr_icon = el("span", "expl_icon");
- instr_icon.classList.add(css_class);
- instr_icon.setAttribute("title", css_class.toUpperCase());
- instr_icon.textContent = format_hex(code);
- const instr_box_text = el("span", "expl_text");
- instr_box_text.textContent = `${param.desc}`;
- instr_box.appendChild(instr_icon);
- instr_box.appendChild(instr_box_text);
- this.instruction_explainer.appendChild(instr_box);
- }
- this.program_memory_cells[pos].classList.add(css_class);
+ this.memory.add_cell_class(pos, name);
});
+ cpu_events.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
+ this.instruction_explainer.add_instruction(instr, pos, code);
- cpu_events.listen(CpuEvent.InstructionExecuted, ({ instr }) => {});
+ this.memory.remove_all_cell_class("instruction_argument");
+ this.memory.remove_all_cell_class("current_instruction");
+ this.memory.add_cell_class(pos, "current_instruction");
+ this.memory.remove_cell_class(pos, "constant", "register", "memory", "invalid");
+ this.memory.add_cell_class(pos, "instruction");
+ });
+ cpu_events.listen(CpuEvent.InvalidParsed, ({ code, pos }) => {
+ this.memory.remove_cell_class(pos, "constant", "register", "memory", "instruction");
+ this.memory.add_cell_class(pos, "invalid");
+ this.instruction_explainer.add_invalid(pos, code);
+ });
}
reset(): void {
this.stop_auto();
- this.program_memory_cells.forEach((c) => {
- c.className = "";
- c.textContent = "00";
- });
this.register_cells.forEach((r) => {
r.textContent = "00";
});
- this.program_counter = 0;
- this.program_memory_cells[0].classList.add("program_counter");
+ this.frequencyIndicator.reset();
+ this.instruction_explainer.reset();
+ this.memory.reset();
this.printout.textContent = "";
}
@@ -173,9 +148,8 @@ export class UI {
if (this.auto_running === false) {
return;
}
- this.events.dispatch(UiEvent.RequestCpuCycle, null);
- setTimeout(loop, speed);
- // requestAnimationFrame(loop);
+ this.events.dispatch(UiEvent.RequestCpuCycle, 1);
+ setTimeout(loop, delay);
};
loop();
}
@@ -183,15 +157,4 @@ export class UI {
stop_auto(): void {
this.auto_running = false;
}
-
- state_update_event(state: ComputerState): void {
- const current_instr = state.current_instruction;
- if (current_instr !== null) {
- this.program_memory_cells[current_instr.pos].classList.add("current_instruction");
- for (let i = 0; i < current_instr.params_found; i++) {
- const offset = i + 1 + current_instr.pos;
- this.program_memory_cells[offset].classList.add("instruction_argument");
- }
- }
- }
}
diff --git a/src/ui/editableHex.ts b/src/ui/editableHex.ts
new file mode 100644
index 0000000..f3eafbe
--- /dev/null
+++ b/src/ui/editableHex.ts
@@ -0,0 +1,122 @@
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function set_caret(el: any, pos: number): boolean {
+ const selection = window.getSelection();
+ const range = document.createRange();
+ if (selection === null) {
+ return false;
+ }
+
+ selection.removeAllRanges();
+ range.selectNode(el);
+
+ range.setStart(el, pos);
+ range.setEnd(el, pos);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ el.focus();
+ return true;
+}
+
+function get_caret(el: HTMLElement): null | number {
+ const sel = window.getSelection();
+ if (sel === null) {
+ return null;
+ }
+ const pos = sel.getRangeAt(0).startOffset;
+ const endPos = pos + Array.from(el.innerHTML.slice(0, pos)).length - el.innerHTML.slice(0, pos).split("").length;
+ return endPos;
+}
+
+const hex_characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
+function replace_non_hex(c: string): string {
+ if (hex_characters.includes(c)) {
+ return c;
+ }
+ return "0";
+}
+
+function editable_constraints(e: Event): boolean {
+ const target = e.target as HTMLDivElement;
+ const text = target.innerHTML ?? "";
+ if (text.length !== 2) {
+ const pos = get_caret(target);
+ const new_str = [...(target.textContent ?? "").substring(0, 2).padStart(2, "0").toUpperCase()]
+ .map(replace_non_hex)
+ .join("");
+ target.innerHTML = "";
+ // For the caret selection to work right, each character must be its own node, complicating this greatly
+ target.append(new_str.substring(0, 1), new_str.substring(1));
+
+ if (pos !== null) {
+ if (pos >= 2) {
+ return true;
+ }
+ set_caret(target, pos);
+ }
+ }
+ return false;
+}
+
+function at(l: Array, i: number): T | null {
+ if (i < 0) {
+ return null;
+ }
+ if (i >= l.length) {
+ return null;
+ }
+ return l[i];
+}
+
+export function make_editable(
+ list: Array,
+ width: number,
+ height: number,
+ on_edit: (n: number, value: string) => void
+): void {
+ for (const [i, cell] of list.entries()) {
+ cell.setAttribute("contenteditable", "true");
+ cell.setAttribute("spellcheck", "false");
+ const next: null | HTMLElement = at(list, i + 1);
+ const prev: null | HTMLElement = at(list, i - 1);
+ const up: null | HTMLElement = at(list, i - width);
+ const down: null | HTMLElement = at(list, i + width);
+ cell.addEventListener("keydown", (e) => {
+ const caret_position = get_caret(cell);
+ const k = e.key;
+ if (k === "ArrowUp") {
+ (up ?? prev)?.focus();
+ cell.blur();
+ } else if (k === "ArrowDown") {
+ (down ?? next)?.focus();
+ cell.blur();
+ } else if ((k === "ArrowLeft" || k === "Backspace") && caret_position === 0) {
+ prev?.focus();
+ cell.blur();
+ } else if (k === "ArrowRight" && caret_position === 1) {
+ next?.focus();
+ cell.blur();
+ } else if (k === "Enter") {
+ cell.blur();
+ } else if (k === "Escape") {
+ cell.blur();
+ return;
+ } else {
+ return;
+ }
+ e.preventDefault();
+ });
+ let previous_text = cell.textContent ?? "";
+ cell.addEventListener("input", (e) => {
+ const current_text = cell.textContent ?? "";
+ if (current_text !== previous_text) {
+ previous_text = cell.textContent ?? "";
+ on_edit(i, current_text);
+ }
+ if (editable_constraints(e) === true) {
+ next?.focus();
+ cell.blur();
+ }
+ });
+ }
+}
diff --git a/src/ui/frequencyIndicator.ts b/src/ui/frequencyIndicator.ts
new file mode 100644
index 0000000..3a689d0
--- /dev/null
+++ b/src/ui/frequencyIndicator.ts
@@ -0,0 +1,52 @@
+import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
+import { UiComponent } from "./uiComponent";
+
+export class frequencyIndicator implements UiComponent {
+ element: HTMLElement;
+ private running: number | null = null;
+ private count: number = 0;
+ private last_value: number = 0;
+ private last_time: number = 0;
+ constructor(element: HTMLElement) {
+ this.element = element;
+ this.start();
+ }
+
+ start(): void {
+ if (this.running !== null) {
+ throw new Error("Tried starting frequencyIndicator twice!");
+ }
+ setInterval(this.update_indicator.bind(this), 1000);
+ }
+
+ stop(): void {
+ if (this.running === null) return;
+ clearInterval(this.running);
+ this.running = null;
+ }
+
+ update_indicator(): void {
+ if (this.last_value !== this.count) {
+ this.element.textContent = `${this.count}hz`;
+ this.last_value = this.count;
+ }
+ this.count = 0;
+ }
+
+ init_events(eh: UiEventHandler): void {
+ this;
+ }
+ clock_cycle(): void {
+ this.count += 1;
+ }
+ reset(): void {
+ this.stop();
+ this.count = 0;
+ this.last_value = 0;
+ }
+ init_cpu_events(c: CpuEventHandler) {
+ c.listen(CpuEvent.ClockCycle, () => {
+ this.count += 1;
+ });
+ }
+}
diff --git a/src/ui/instructionExplainer.ts b/src/ui/instructionExplainer.ts
new file mode 100644
index 0000000..ede412e
--- /dev/null
+++ b/src/ui/instructionExplainer.ts
@@ -0,0 +1,55 @@
+import { el, format_hex, u8 } from "../etc";
+import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
+import { Instruction, ParamType, ParameterType } from "../instructionSet";
+import { UiComponent } from "./uiComponent";
+
+export class InstructionExplainer implements UiComponent {
+ element: HTMLElement;
+ constructor(element: HTMLElement) {
+ this.element = element;
+ }
+ add_instruction(instr: Instruction, pos: u8, byte: u8): void {
+ this.reset();
+ this.add_box(format_hex(byte), instr.name, "instruction");
+ }
+
+ private add_box(box_icon_text: string, name: string, css_class: string): void {
+ const instr_box = el("div", "expl_box");
+ const instr_icon = el("span", "expl_icon");
+ instr_icon.classList.add(css_class);
+ instr_icon.setAttribute("title", css_class.toUpperCase());
+ instr_icon.textContent = box_icon_text;
+ const instr_box_text = el("span", "expl_text");
+ instr_box_text.textContent = name;
+ instr_box.appendChild(instr_icon);
+ instr_box.appendChild(instr_box_text);
+ this.element.appendChild(instr_box);
+ }
+
+ add_param(param: ParameterType, pos: u8, byte: u8): void {
+ const t = param.type;
+ let name;
+ if (t === ParamType.Const) {
+ name = "constant";
+ } else if (t === ParamType.Memory) {
+ name = "memory";
+ } else if (t === ParamType.Register) {
+ name = "register";
+ } else {
+ throw new Error("unreachable");
+ }
+ this.add_box(format_hex(byte), param.desc, name);
+ }
+
+ add_invalid(pos: u8, byte: u8) {
+ this.reset();
+ this.add_box(format_hex(byte), "Invalid Instruction", "invalid");
+ }
+
+ init_events(eh: UiEventHandler): void {}
+ init_cpu_events(c: CpuEventHandler) {}
+
+ reset(): void {
+ this.element.innerHTML = "";
+ }
+}
diff --git a/src/ui/memoryView.ts b/src/ui/memoryView.ts
new file mode 100644
index 0000000..a1de6e0
--- /dev/null
+++ b/src/ui/memoryView.ts
@@ -0,0 +1,78 @@
+import { el, format_hex, u8 } from "../etc";
+import { CpuEventHandler, UiEventHandler } from "../events";
+import { UiComponent } from "./uiComponent";
+
+export enum CellTag {
+ ProgramCounter = "program_counter",
+}
+
+type MemoryCell = {
+ el: HTMLElement;
+};
+
+export class MemoryView implements UiComponent {
+ element: HTMLElement;
+ cells: Array = [];
+ program_counter: number = 0;
+ constructor(element: HTMLElement) {
+ this.element = element;
+ for (let i = 0; i < 256; i++) {
+ const mem_cell_el = el("div");
+ mem_cell_el.textContent = "00";
+ element.appendChild(mem_cell_el);
+ const mem_cell = { el: mem_cell_el, tags: [] };
+ this.cells.push(mem_cell);
+ }
+ this.set_program_counter(0);
+ }
+
+ add_cell_class(address: u8, ...css_class: string[]): void {
+ for (const str of css_class) {
+ this.cells[address].el.classList.add(str);
+ }
+ }
+
+ remove_cell_class(address: u8, ...css_class: string[]): void {
+ for (const str of css_class) {
+ this.cells[address].el.classList.remove(str);
+ }
+ }
+
+ remove_all_cell_class(css_class: string): void {
+ for (const cell of this.cells) {
+ cell.el.classList.remove(css_class);
+ }
+ }
+
+ add_cell_class_exclusive(address: u8, css_class: string): void {
+ this.remove_all_cell_class(css_class);
+ this.add_cell_class(address, css_class);
+ }
+
+ set_cell_value(address: u8, value: u8): void {
+ this.cells[address].el.textContent = format_hex(value);
+ }
+
+ set_program_counter(position: number): void {
+ this.cells[this.program_counter].el.classList.remove("program_counter");
+ this.cells[position].el.classList.add("program_counter");
+ this.program_counter = position;
+ }
+
+ reset(): void {
+ this.element.innerHTML = "";
+ for (let i = 0; i < 256; i++) {
+ const mem_cell_el = el("div");
+ mem_cell_el.textContent = "00";
+ this.element.appendChild(mem_cell_el);
+ const mem_cell = { el: mem_cell_el };
+ this.cells.push(mem_cell);
+ }
+ this.set_program_counter(0);
+ }
+
+ init_events(eh: UiEventHandler): void {
+ this;
+ }
+ init_cpu_events(c: CpuEventHandler) {}
+}
diff --git a/src/ui/uiComponent.ts b/src/ui/uiComponent.ts
new file mode 100644
index 0000000..c7d281e
--- /dev/null
+++ b/src/ui/uiComponent.ts
@@ -0,0 +1,9 @@
+import { u8 } from "../etc";
+import { CpuEventHandler, UiEventHandler } from "../events";
+
+export interface UiComponent {
+ element: HTMLElement;
+ reset: () => void;
+ init_events: (ui: UiEventHandler) => void;
+ init_cpu_events: (c: CpuEventHandler) => void;
+}
diff --git a/style.css b/style.css
deleted file mode 100644
index 7d12fd3..0000000
--- a/style.css
+++ /dev/null
@@ -1,136 +0,0 @@
-* {
- box-sizing: border-box;
-}
-
-#memory {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
- grid-gap: 5px;
- padding: 10px;
- border: 5px solid yellow;
- /* color: lightgray; */
-}
-
-pre {
- font-size: 0.5em;
-}
-
-#memory .instruction {
- color: greenyellow;
-}
-#memory .constant {
- color: purple;
-}
-#memory .register {
- color: orange;
-}
-#memory .memory {
- color: pink;
-}
-#memory .invalid_instruction {
- color: maroon;
-}
-
-#memory div {
- aspect-ratio: 1;
- text-align: center;
- margin: auto;
-}
-
-body {
- color: gray;
- background-color: black;
- font-size: 2.5em;
- font-family: monospace;
-}
-
-#registers div {
- max-height: min-content;
- margin-block: auto;
-}
-
-#subcontainer {
- display: flex;
- flex-direction: row;
- column-gap: 15px;
-}
-
-#labelcontainer {
- column-gap: 18px;
- font-size: 0.85em;
- display: flex;
- align-items: center;
- justify-content: space-between;
- user-select: none;
-}
-
-#main {
- display: flex;
- flex-direction: row;
-}
-
-#title {
- writing-mode: vertical-lr;
- text-align: left;
- user-select: none;
- transform: scale(-1, -1);
-}
-
-#printout {
- border: 4px dashed yellow;
- width: 1000px;
- padding: 10px;
- margin-left: 20px;
- word-wrap: break-word;
- word-break: break-all;
-}
-
-#memory div.program_counter {
- outline: 3px solid orange;
-}
-
-#memory div.instruction_argument {
- outline: 3px dashed purple;
-}
-#memory div.current_instruction {
- outline: 3px dashed greenyellow;
-}
-
-#registers {
- border: 5px solid yellow;
- border-bottom: none;
- grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
- max-width: fit-content;
- display: grid;
- column-gap: 5px;
- color: lightgray;
- padding: 10px;
-}
-
-#container {
- font-family: monospace;
- max-width: min-content;
- max-height: min-content;
-}
-
-button,
-label.button {
- border: 4px solid yellow;
- color: gray;
- margin: 10px;
- margin-inline: 8px;
- padding: 10px;
- font-size: 0.8em;
- font-family: monospace;
- background-color: transparent;
- cursor: pointer;
- user-select: none;
-}
-button:hover,
-label.button:hover {
- color: white;
-}
-#controls_bar {
- margin-left: 1.12em;
- display: flex;
-}
diff --git a/webpack.config.js b/webpack.config.js
index 944d810..60de58d 100755
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -11,12 +11,23 @@ const config = {
},
{
test: /\.html/,
- type: "asset/resource"
- }
+ type: "asset/resource",
+ },
+ {
+ test: /\.s[ac]ss$/i,
+ use: [
+ // Creates `style` nodes from JS strings
+ "style-loader",
+ // Translates CSS into CommonJS
+ "css-loader",
+ // Compiles Sass to CSS
+ "sass-loader",
+ ],
+ },
],
},
resolve: {
- extensions: [".ts"],
+ extensions: [".ts", ".scss", ".css"],
},
output: {
filename: "main.js",
@@ -32,4 +43,4 @@ module.exports = (env, argv) => {
}
return config;
-};
\ No newline at end of file
+};