├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .jsdocrc ├── .webpackrc ├── LICENSE ├── Makefile ├── README.md ├── dist ├── chip3.js └── index.html ├── package.json ├── src ├── Assembler │ ├── Assembler.js │ ├── Parser.js │ ├── index.js │ ├── instructions.js │ ├── types.jsdoc │ └── utils.js ├── System │ ├── CPU.js │ ├── Printer.js │ ├── RAM.js │ ├── System.js │ └── index.js ├── Workbench │ ├── App.jsx │ ├── Button.jsx │ ├── CPUPane.jsx │ ├── Component.jsx │ ├── HistoryPane.jsx │ ├── Layout.jsx │ ├── PrinterPane.jsx │ ├── RAMPane.jsx │ ├── Timer.jsx │ ├── Toolbar.jsx │ ├── index.html │ ├── index.js │ └── utils.js └── index.js └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "react-hot-loader/babel", 4 | "transform-decorators-legacy" 5 | ], 6 | "presets": [ 7 | ["es2015", {"modules":false}], 8 | "stage-0", 9 | "react" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tabs 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | 4 | "parserOptions": { 5 | "sourceType": "module", 6 | "ecmaVersion": 7, 7 | "ecmaFeatures": { 8 | "globalReturn": false, 9 | "impliedStrict": true, 10 | "experimentalObjectRestSpread": true, 11 | "jsx": true, 12 | }, 13 | }, 14 | 15 | "settings": { 16 | "react": { 17 | "pragma": "React", 18 | "version": "15.0", 19 | } 20 | }, 21 | 22 | "env": { 23 | "browser": true, 24 | "node": true, 25 | "mocha": true, 26 | "es6": true, 27 | }, 28 | 29 | "plugins": [ 30 | "babel", 31 | "react", 32 | ], 33 | 34 | "rules": { 35 | 36 | // Possible Errors 37 | // --------------- 38 | // These rules relate to possible syntax or logic errors in JavaScript code: 39 | 40 | // Require or disallow trailing commas 41 | "comma-dangle": ["error", "always-multiline"], 42 | // Disallow assignment operators in conditional expressions 43 | "no-cond-assign": ["error", "except-parens"], 44 | // Disallow the use of console 45 | "no-console": "error", 46 | // Disallow constant expressions in conditions 47 | "no-constant-condition": "error", 48 | // Disallow control characters in regular expressions 49 | "no-control-regex": "error", 50 | // Disallow the use of debugger 51 | "no-debugger": "error", 52 | // Disallow duplicate arguments in function definitions 53 | "no-dupe-args": "error", 54 | // Disallow duplicate keys in object literals 55 | "no-dupe-keys": "error", 56 | // Disallow duplicate case labels 57 | "no-duplicate-case": "error", 58 | // Disallow empty block statements 59 | "no-empty": "error", 60 | // Disallow empty character classes in regular expressions 61 | "no-empty-character-class": "error", 62 | // Disallow reassigning exceptions in catch clauses 63 | "no-ex-assign": "error", 64 | // Disallow unnecessary boolean casts 65 | "no-extra-boolean-cast": "error", 66 | // Disallow unnecessary parentheses 67 | "no-extra-parens": "off", 68 | // Disallow unnecessary semicolons 69 | "no-extra-semi": "error", 70 | // Disallow reassigning function declarations 71 | "no-func-assign": "error", 72 | // Disallow function or var declarations in nested blocks 73 | "no-inner-declarations": "off", 74 | // Disallow invalid regular expression strings in RegExp constructors 75 | "no-invalid-regexp": "error", 76 | // Disallow irregular whitespace outside of strings and comments 77 | "no-irregular-whitespace": "error", 78 | // Disallow negating the left operand in in expressions 79 | "no-negated-in-lhs": "error", 80 | // Disallow calling global object properties as functions 81 | "no-obj-calls": "error", 82 | // Disallow multiple spaces in regular expression literals 83 | "no-regex-spaces": "error", 84 | // Disallow sparse arrays 85 | "no-sparse-arrays": "error", 86 | // Disallow confusing multiline expressions 87 | "no-unexpected-multiline": "error", 88 | // Disallow unreachable code after return, throw, continue, and break statements 89 | "no-unreachable": "error", 90 | // Disallow control flow statements in finally blocks 91 | "no-unsafe-finally": "error", 92 | // Require calls to isNaN() when checking for NaN 93 | "use-isnan": "error", 94 | // Enforce valid JSDoc comments 95 | "valid-jsdoc": ["error", { 96 | "prefer": { 97 | "return": "returns" 98 | }, 99 | "requireReturn": false, 100 | "requireReturnDescription": false, 101 | "requireParamDescription": false 102 | }], 103 | // Enforce comparing typeof expressions against valid strings 104 | "valid-typeof": "error", 105 | 106 | // Best Practices 107 | // -------------- 108 | // These rules relate to better ways of doing things to help you avoid problems: 109 | 110 | // Enforce getter and setter pairs in objects 111 | "accessor-pairs": "error", 112 | // Enforce return statements in callbacks of array methods 113 | "array-callback-return": "error", 114 | // Enforce the use of variables within the scope they are defined 115 | "block-scoped-var": "off", 116 | // Enforce a maximum cyclomatic complexity allowed in a program 117 | "complexity": ["warn", 20], 118 | // Require return statements to either always or never specify values 119 | "consistent-return": "off", 120 | // Enforce consistent brace style for all control statements 121 | "curly": ["error", "multi-line", "consistent"], 122 | // Require default cases in switch statements 123 | "default-case": "off", 124 | // Enforce consistent newlines before and after dots 125 | "dot-location": ["error", "property"], 126 | // Enforce dot notation whenever possible 127 | "dot-notation": "error", 128 | // Require the use of === and !== 129 | "eqeqeq": ["error", "allow-null"], 130 | // Require for-in loops to include an if statement 131 | "guard-for-in": "off", 132 | // Disallow the use of alert, confirm, and prompt 133 | "no-alert": "error", 134 | // Disallow the use of arguments.caller or arguments.callee 135 | "no-caller": "error", 136 | // Disallow lexical declarations in case clauses 137 | "no-case-declarations": "error", 138 | // Disallow division operators explicitly at the beginning of regular expressions 139 | "no-div-regex": "off", 140 | // Disallow else blocks after return statements in if statements 141 | "no-else-return": "off", 142 | // Disallow empty functions 143 | "no-empty-function": "error", 144 | // Disallow empty destructuring patterns 145 | "no-empty-pattern": "error", 146 | // Disallow null comparisons without type-checking operators 147 | "no-eq-null": "off", 148 | // Disallow the use of eval() 149 | "no-eval": "error", 150 | // Disallow extending native types 151 | "no-extend-native": "error", 152 | // Disallow unnecessary calls to .bind() 153 | "no-extra-bind": "error", 154 | // Disallow unnecessary labels 155 | "no-extra-label": "error", 156 | // Disallow fallthrough of case statements 157 | "no-fallthrough": "error", 158 | // Disallow leading or trailing decimal points in numeric literals 159 | "no-floating-decimal": "error", 160 | // Disallow shorthand type conversions 161 | "no-implicit-coercion": "error", 162 | // Disallow var and named function declarations in the global scope 163 | "no-implicit-globals": "error", 164 | // Disallow the use of eval()-like methods 165 | "no-implied-eval": "error", 166 | // Disallow this keywords outside of classes or class-like objects 167 | // NOTE: disabled for false positives 168 | "no-invalid-this": "off", 169 | // Disallow the use of the __iterator__ property 170 | "no-iterator": "error", 171 | // Disallow labeled statements 172 | "no-labels": "error", 173 | // Disallow unnecessary nested blocks 174 | "no-lone-blocks": "error", 175 | // Disallow function declarations and expressions inside loop statements 176 | "no-loop-func": "error", 177 | // Disallow magic numbers 178 | "no-magic-numbers": "off", 179 | // Disallow multiple spaces 180 | "no-multi-spaces": "off", 181 | // Disallow multiline strings 182 | "no-multi-str": "error", 183 | // Disallow reassigning native objects 184 | "no-native-reassign": "error", 185 | // Disallow new operators outside of assignments or comparisons 186 | "no-new": "error", 187 | // Disallow new operators with the Function object 188 | "no-new-func": "error", 189 | // Disallow new operators with the String, Number, and Boolean objects 190 | "no-new-wrappers": "error", 191 | // Disallow octal literals 192 | "no-octal": "error", 193 | // Disallow octal escape sequences in string literals 194 | "no-octal-escape": "error", 195 | // Disallow reassigning function parameters 196 | "no-param-reassign": "off", 197 | // Disallow the use of the __proto__ property 198 | "no-proto": "error", 199 | // Disallow var redeclaration 200 | "no-redeclare": "error", 201 | // Disallow assignment operators in return statements 202 | "no-return-assign": "off", 203 | // Disallow javascript: urls 204 | "no-script-url": "error", 205 | // Disallow assignments where both sides are exactly the same 206 | "no-self-assign": "error", 207 | // Disallow comparisons where both sides are exactly the same 208 | "no-self-compare": "error", 209 | // Disallow comma operators 210 | "no-sequences": "off", 211 | // Disallow throwing literals as exceptions 212 | "no-throw-literal": "error", 213 | // Disallow unmodified loop conditions 214 | "no-unmodified-loop-condition": "error", 215 | // Disallow unused expressions 216 | "no-unused-expressions": "error", 217 | // Disallow unused labels 218 | "no-unused-labels": "error", 219 | // Disallow unnecessary calls to .call() and .apply() 220 | "no-useless-call": "error", 221 | // Disallow unnecessary concatenation of literals or template literals 222 | "no-useless-concat": "error", 223 | // Disallow unnecessary escape characters 224 | "no-useless-escape": "error", 225 | // Disallow void operators 226 | "no-void": "error", 227 | // Disallow specified warning terms in comments 228 | "no-warning-comments": "warn", 229 | // Disallow with statements 230 | "no-with": "error", 231 | // Enforce the consistent use of the radix argument when using parseInt() 232 | "radix": "error", 233 | // Require var declarations be placed at the top of their containing scope 234 | "vars-on-top": "off", 235 | // Require parentheses around immediate function invocations 236 | "wrap-iife": ["error", "inside"], 237 | // Require or disallow “Yoda” conditions 238 | "yoda": ["error", "never"], 239 | 240 | // Strict Mode 241 | // ----------- 242 | // These rules relate to strict mode directives: 243 | 244 | // Require or disallow strict mode directives 245 | "strict": ["error", "safe"], 246 | 247 | // Variables 248 | // --------- 249 | // These rules relate to variable declarations: 250 | 251 | // Require or disallow initialization in var declarations 252 | "init-declarations": "off", 253 | // Disallow catch clause parameters from shadowing variables in the outer scope 254 | "no-catch-shadow": "off", 255 | // Disallow deleting variables 256 | "no-delete-var": "error", 257 | // Disallow labels that share a name with a variable 258 | "no-label-var": "error", 259 | // Disallow specified global variables 260 | "no-restricted-globals": "off", 261 | // Disallow var declarations from shadowing variables in the outer scope 262 | "no-shadow": "off", 263 | // Disallow identifiers from shadowing restricted names 264 | "no-shadow-restricted-names": "off", 265 | // Disallow the use of undeclared variables unless mentioned in /*global */ comments 266 | "no-undef": "error", 267 | // Disallow initializing variables to undefined 268 | "no-undef-init": "error", 269 | // Disallow the use of undefined as an identifier 270 | "no-undefined": "error", 271 | // Disallow unused variables 272 | "no-unused-vars": ["error", { 273 | "vars": "all", 274 | "args": "none", 275 | "caughtErrors": "all", 276 | }], 277 | // Disallow the use of variables before they are defined 278 | "no-use-before-define": ["error", "nofunc"], 279 | 280 | // Node.js and CommonJS 281 | // -------------------- 282 | // These rules relate to code running in Node.js, or in browsers with CommonJS: 283 | 284 | // Require return statements after callbacks 285 | "callback-return": "error", 286 | // Require require() calls to be placed at top-level module scope 287 | "global-require": "off", 288 | // Require error handling in callbacks 289 | "handle-callback-err": "error", 290 | // Disallow require calls to be mixed with regular var declarations 291 | "no-mixed-requires": "error", 292 | // Disallow new operators with calls to require 293 | "no-new-require": "error", 294 | // Disallow string concatenation with __dirname and __filename 295 | "no-path-concat": "error", 296 | // Disallow the use of process.env 297 | "no-process-env": "off", 298 | // Disallow the use of process.exit() 299 | "no-process-exit": "off", 300 | // Disallow specified modules when loaded by require 301 | "no-restricted-modules": "off", 302 | // Disallow synchronous methods 303 | "no-sync": "error", 304 | 305 | // Stylistic Issues 306 | // ---------------- 307 | // These rules relate to style guidelines, and are therefore quite subjective: 308 | 309 | // Enforce consistent spacing inside array brackets 310 | // NOTE: Disabled as it's handled by eslint-plugin-babel 311 | "array-bracket-spacing": "off", 312 | // Enforce consistent spacing inside single-line blocks 313 | "block-spacing": ["error", "always"], 314 | // Enforce consistent brace style for blocks 315 | "brace-style": ["error", "1tbs", { 316 | "allowSingleLine": true 317 | }], 318 | // Enforce camelcase naming convention 319 | "camelcase": "error", 320 | // Enforce consistent spacing before and after commas 321 | "comma-spacing": ["error", { 322 | "before": false, 323 | "after": true, 324 | }], 325 | // Enforce consistent comma style 326 | "comma-style": ["error", "last"], 327 | // Enforce consistent spacing inside computed property brackets 328 | "computed-property-spacing": ["error", "never"], 329 | // Enforce consistent naming when capturing the current execution context 330 | "consistent-this": ["error", "self"], 331 | // Enforce at least one newline at the end of files 332 | "eol-last": ["error", "unix"], 333 | // Enforce named function expressions 334 | // NOTE: Disabled as we like anons 335 | "func-names": "off", 336 | // Enforce the consistent use of either function declarations or expressions 337 | "func-style": ["error", "declaration", { 338 | "allowArrowFunctions": true, 339 | }], 340 | // Disallow specified identifiers 341 | "id-blacklist": "off", 342 | // Enforce minimum and maximum identifier lengths 343 | "id-length": "off", 344 | // Require identifiers to match a specified regular expression 345 | "id-match": "off", 346 | // Enforce consistent indentation 347 | "indent": ["error", "tab", { 348 | "SwitchCase": 1 349 | }], 350 | // Enforce the consistent use of either double or single quotes in JSX attributes 351 | "jsx-quotes": ["error", "prefer-double"], 352 | // Enforce consistent spacing between keys and values in object literal properties 353 | "key-spacing": "off", 354 | // Enforce consistent spacing before and after keywords 355 | "keyword-spacing": ["error", { 356 | "before": true, 357 | "after": true, 358 | }], 359 | // Enforce consistent linebreak style 360 | "linebreak-style": ["error", "unix"], 361 | // Require empty lines around comments 362 | "lines-around-comment": "off", 363 | // Enforce a maximum depth that blocks can be nested 364 | "max-depth": ["error", { 365 | "max": 3 366 | }], 367 | // Enforce a maximum line length 368 | "max-len": ["error", { 369 | "code": 80, 370 | "tabWidth": 2, 371 | "ignoreUrls": true 372 | }], 373 | // Enforce a maximum depth that callbacks can be nested 374 | "max-nested-callbacks": ["error", { 375 | "max": 3, 376 | }], 377 | // Enforce a maximum number of parameters in function definitions 378 | "max-params": ["error", { 379 | "max": 6, 380 | }], 381 | // Enforce a maximum number of statements allowed in function blocks 382 | "max-statements": "off", 383 | // Enforce a maximum number of statements allowed per line 384 | "max-statements-per-line": ["error", { 385 | "max": 3, 386 | }], 387 | // Require constructor function names to begin with a capital letter 388 | // NOTE: Disabled as it's handled by eslint-plugin-babel 389 | "new-cap": "off", 390 | // Require parentheses when invoking a constructor with no arguments 391 | "new-parens": "error", 392 | // Require or disallow an empty line after var declarations 393 | "newline-after-var": "off", 394 | // Require an empty line before return statements 395 | "newline-before-return": "off", 396 | // Require a newline after each call in a method chain 397 | "newline-per-chained-call": "off", 398 | // Disallow Array constructors 399 | "no-array-constructor": "off", 400 | // Disallow bitwise operators 401 | "no-bitwise": "error", 402 | // Disallow continue statements 403 | "no-continue": "off", 404 | // Disallow inline comments after code 405 | "no-inline-comments": "off", 406 | // Disallow if statements as the only statement in else blocks 407 | "no-lonely-if": "error", 408 | // Disallow mixed spaces and tabs for indentation 409 | "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], 410 | // Disallow multiple empty lines 411 | "no-multiple-empty-lines": ["error", { 412 | "max": 2, 413 | }], 414 | // Disallow negated conditions 415 | "no-negated-condition": "off", 416 | // Disallow nested ternary expressions 417 | "no-nested-ternary": "error", 418 | // Disallow Object constructors 419 | "no-new-object": "error", 420 | // Disallow the unary operators ++ and -- 421 | "no-plusplus": "off", 422 | // Disallow specified syntax 423 | "no-restricted-syntax": "off", 424 | // Disallow spacing between function identifiers and their applications 425 | "no-spaced-func": "error", 426 | // Disallow ternary operators 427 | "no-ternary": "off", 428 | // Disallow trailing whitespace at the end of lines 429 | "no-trailing-spaces": "error", 430 | // Disallow dangling underscores in identifiers 431 | "no-underscore-dangle": "error", 432 | // Disallow ternary operators when simpler alternatives exist 433 | "no-unneeded-ternary": "error", 434 | // Disallow whitespace before properties 435 | "no-whitespace-before-property": "error", 436 | // Enforce consistent spacing inside braces 437 | // NOTE: Disabled as it's handled by eslint-plugin-babel 438 | "object-curly-spacing": "off", 439 | // Enforce placing object properties on separate lines 440 | "object-property-newline": "off", 441 | // Enforce variables to be declared either together or separately in functions 442 | "one-var": ["error", "never"], 443 | // Require or disallow newlines around var declarations 444 | "one-var-declaration-per-line": "off", 445 | // Require or disallow assignment operator shorthand where possible 446 | "operator-assignment": ["error", "always"], 447 | // Enforce consistent linebreak style for operators 448 | // NOTE: Off to relax style when needed 449 | "operator-linebreak": ["off", "after"], 450 | // Require or disallow padding within blocks 451 | "padded-blocks": "off", 452 | // Require quotes around object literal property names 453 | "quote-props": ["error", "as-needed"], 454 | // Enforce the consistent use of either backticks, double, or single quotes 455 | "quotes": ["error", "single", { 456 | "avoidEscape": true, 457 | "allowTemplateLiterals": true, 458 | }], 459 | // Require JSDoc comments 460 | "require-jsdoc": "error", 461 | // Require or disallow semicolons instead of ASI 462 | "semi": ["error", "always"], 463 | // Enforce consistent spacing before and after semicolons 464 | "semi-spacing": ["error", { 465 | "before": false, 466 | "after": true, 467 | }], 468 | // Require variables within the same declaration block to be sorted 469 | "sort-vars": "off", 470 | // Enforce consistent spacing before blocks 471 | "space-before-blocks": ["error", "always"], 472 | // Enforce consistent spacing before function definition opening parenthesis 473 | "space-before-function-paren": ["error", "never"], 474 | // Enforce consistent spacing inside parentheses 475 | "space-in-parens": ["error", "never"], 476 | // Require spacing around operators 477 | "space-infix-ops": "off", 478 | // Enforce consistent spacing before or after unary operators 479 | "space-unary-ops": ["error", { 480 | "words": true, 481 | "nonwords": false, 482 | }], 483 | // Enforce consistent spacing after the // Or /* in a comment 484 | "spaced-comment": ["error", "always"], 485 | // Require parenthesis around regex literals 486 | "wrap-regex": "off", 487 | 488 | // ECMAScript 6 489 | // ------------ 490 | // These rules relate to ES6, also known as ES2015: 491 | 492 | // Require braces around arrow function bodies 493 | "arrow-body-style": ["error", "as-needed"], 494 | // Require parentheses around arrow function arguments 495 | // NOTE: Disabled as it's handled by eslint-plugin-babel 496 | "arrow-parens": "off", 497 | // Enforce consistent spacing before and after the arrow in arrow functions 498 | "arrow-spacing": ["error", { 499 | "before": true, 500 | "after": true, 501 | }], 502 | // Require super() calls in constructors 503 | "constructor-super": "error", 504 | // Enforce consistent spacing around * operators in generator functions 505 | "generator-star-spacing": "off", 506 | // Disallow reassigning class members 507 | "no-class-assign": "error", 508 | // Disallow arrow functions where they could be confused with comparisons 509 | // NOTE: Disabled because of false positives 510 | "no-confusing-arrow": ["off", { 511 | "allowParens": true 512 | }], 513 | // Disallow reassigning const variables 514 | "no-const-assign": "error", 515 | // Disallow duplicate class members 516 | "no-dupe-class-members": "error", 517 | // Disallow duplicate module imports 518 | "no-duplicate-imports": "off", 519 | // Disallow new operators with the Symbol object 520 | "no-new-symbol": "error", 521 | // Disallow specified modules when loaded by import 522 | "no-restricted-imports": "off", 523 | // Disallow this/super before calling super() in constructors 524 | "no-this-before-super": "error", 525 | // Disallow unnecessary computed property keys in object literals 526 | "no-useless-computed-key": "error", 527 | // Disallow unnecessary constructors 528 | "no-useless-constructor": "error", 529 | // Require let or const instead of var 530 | "no-var": "error", 531 | // Require or disallow method and property shorthand syntax for object literals 532 | // NOTE: Disabled as it's handled by eslint-plugin-babel 533 | "object-shorthand": "off", 534 | // Require arrow functions as callbacks 535 | "prefer-arrow-callback": ["error", { 536 | "allowNamedFunctions": true, 537 | "allowUnboundThis": false, 538 | }], 539 | // Require const declarations for variables that are never reassigned after declared 540 | "prefer-const": ["error", { 541 | "destructuring": "any", 542 | }], 543 | // Require Reflect methods where applicable 544 | "prefer-reflect": "error", 545 | // Require rest parameters instead of arguments 546 | "prefer-rest-params": "error", 547 | // Require spread operators instead of .apply() 548 | "prefer-spread": "error", 549 | // Require template literals instead of string concatenation 550 | "prefer-template": "error", 551 | // Require generator functions to contain yield 552 | "require-yield": "error", 553 | // Enforce sorted import declarations within modules 554 | "sort-imports": "off", 555 | // Require or disallow spacing around embedded expressions of template strings 556 | "template-curly-spacing": ["error", "never"], 557 | // Require or disallow spacing around the * in yield* expressions 558 | "yield-star-spacing": ["error", { 559 | "before": false, 560 | "after": true, 561 | }], 562 | 563 | // Babel 564 | // ----- 565 | // These rules are reimplemented by Babel 566 | 567 | // Handles async/await functions correctly 568 | "babel/generator-star-spacing": ["error", { 569 | "before": false, 570 | "after": true, 571 | }], 572 | // Ignores capitalized decorators (@Decorator) 573 | "babel/new-cap": "error", 574 | // Handles destructuring arrays with flow type in function parameters 575 | "babel/array-bracket-spacing": ["error", "never"], 576 | // doesn't complain about export x from "mod"; or export * as x from "mod"; 577 | "babel/object-curly-spacing": ["error", "never"], 578 | // doesn't fail when using object spread (...obj) 579 | "babel/object-shorthand": ["error", "always"], 580 | // Handles async functions correctly 581 | // NOTE: Disabled as it does not allow single-line no-parens 582 | "babel/arrow-parens": ["off", "as-needed"], 583 | // Guard against awaiting async functions inside of a loop 584 | "babel/no-await-in-loop": "error", 585 | // Require a particular separator between properties in Flow object types. 586 | "babel/flow-object-type": ["error", "comma"], 587 | 588 | // React 589 | // ----- 590 | // These rules relate to React: 591 | 592 | // Prevent missing displayName in a React component definition 593 | // 594 | "react/display-name": "error", 595 | // Forbid certain propTypes 596 | "react/forbid-prop-types": "off", 597 | // Prevent usage of dangerous JSX properties 598 | "react/no-danger": "error", 599 | // Prevent usage of deprecated methods 600 | "react/no-deprecated": "error", 601 | // Prevent usage of setState in componentDidMount 602 | "react/no-did-mount-set-state": "error", 603 | // Prevent usage of setState in componentDidUpdate 604 | "react/no-did-update-set-state": "error", 605 | // Prevent direct mutation of this.state 606 | "react/no-direct-mutation-state": "error", 607 | // Prevent usage of isMounted 608 | "react/no-is-mounted": "error", 609 | // Prevent multiple component definition per file 610 | "react/no-multi-comp": "off", 611 | // Prevent usage of setState 612 | "react/no-set-state": "off", 613 | // Prevent using string references in ref attribute. 614 | "react/no-string-refs": "error", 615 | // Prevent usage of unknown DOM property (fixable) 616 | "react/no-unknown-property": "error", 617 | // Enforce ES5 or ES6 class for React Components 618 | "react/prefer-es6-class": ["error", "always"], 619 | // Enforce stateless React Components to be written as a pure function 620 | "react/prefer-stateless-function": "off", 621 | // Prevent missing props validation in a React component definition 622 | "react/prop-types": "error", 623 | // Prevent missing React when using JSX 624 | "react/react-in-jsx-scope": "error", 625 | // Restrict file extensions that may be required 626 | "react/require-extension": ["error", { 627 | "extensions": [".js", ".jsx"] 628 | }], 629 | // Enforce ES5 or ES6 class for returning value in render function 630 | "react/require-render-return": "error", 631 | // Prevent extra closing tags for components without children 632 | "react/self-closing-comp": "error", 633 | // Enforce component methods order 634 | // NOTE: Disabled because it does not allow renderX after render 635 | "react/sort-comp": "off", 636 | // Enforce propTypes declarations alphabetical sorting 637 | "react/sort-prop-types": "error", 638 | // Prevent missing parentheses around multilines JSX (fixable) 639 | "react/wrap-multilines": "error", 640 | 641 | // JSX-specific rules 642 | // ------------------ 643 | // These rules relate to JSX specifically: 644 | 645 | // Enforce boolean attributes notation in JSX (fixable) 646 | "react/jsx-boolean-value": ["error", "never"], 647 | // Validate closing bracket location in JSX (fixable) 648 | "react/jsx-closing-bracket-location": "off", 649 | // Enforce or disallow spaces inside of curly braces in JSX attributes (fixable) 650 | "react/jsx-curly-spacing": ["error", "never"], 651 | // Enforce or disallow spaces around equal signs in JSX attributes (fixable) 652 | "react/jsx-equals-spacing": ["error", "never"], 653 | // Enforce position of the first prop in JSX 654 | "react/jsx-first-prop-new-line": "off", 655 | // Enforce event handler naming conventions in JSX 656 | "react/jsx-handler-names": "off", 657 | // Validate JSX indentation 658 | "react/jsx-indent": ["error", "tab"], 659 | // Validate props indentation in JSX (fixable) 660 | "react/jsx-indent-props": ["error", "tab"], 661 | // Validate JSX has key prop when in array or iterator 662 | "react/jsx-key": "error", 663 | // Limit maximum of props on a single line in JSX 664 | "react/jsx-max-props-per-line": ["off"], 665 | // Prevent usage of .bind() and arrow functions in JSX props 666 | "react/jsx-no-bind": ["error", { 667 | "ignoreRefs": true, 668 | "allowArrowFunctions": false, 669 | "allowBind": false, 670 | }], 671 | // Prevent duplicate props in JSX 672 | "react/jsx-no-duplicate-props": "error", 673 | // Prevent usage of unwrapped JSX strings 674 | "react/jsx-no-literals": "off", 675 | // Prevent usage of unsafe target='_blank' 676 | "react/jsx-no-target-blank": "error", 677 | // Disallow undeclared variables in JSX 678 | "react/jsx-no-undef": "error", 679 | // Enforce PascalCase for user-defined JSX components 680 | "react/jsx-pascal-case": "error", 681 | // Enforce props alphabetical sorting 682 | "react/jsx-sort-props": "off", 683 | // Validate spacing before closing bracket in JSX (fixable) 684 | "react/jsx-space-before-closing": ["error", "never"], 685 | // Prevent React to be incorrectly marked as unused 686 | "react/jsx-uses-react": "error", 687 | // Prevent variables used in JSX to be incorrectly marked as unused 688 | "react/jsx-uses-vars": "error", 689 | } 690 | } 691 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | *~ 4 | ~* 5 | tmp 6 | *.tmp 7 | *.log 8 | *.bak 9 | *.orig 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.jsdocrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "source": { 4 | "include": ["./README.md", "./src"], 5 | "includePattern": ".+\\.js(doc)?$", 6 | "excludePattern": "(^|\\/|\\\\)_" 7 | }, 8 | 9 | "tags": { 10 | "allowUnknownTags": true 11 | }, 12 | 13 | "opts": { 14 | "recurse": true, 15 | "destination": "./docs/" 16 | }, 17 | 18 | "templates": { 19 | "cleverLinks": true, 20 | "monospaceLinks": false 21 | }, 22 | 23 | "plugins": [ 24 | "plugins/markdown", 25 | "node_modules/jsdoc-babel" 26 | ], 27 | 28 | "babel": { 29 | "extensions": ["js", "jsx"], 30 | "stage": 0, 31 | "compact": false, 32 | "comments": true 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /.webpackrc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const WebpackShellPlugin = require('webpack-shell-plugin'); 4 | const development = process.env.NODE_ENV === 'development'; 5 | function d(dir) { return path.resolve(__dirname, dir); } 6 | 7 | module.exports = { 8 | 9 | entry: { 10 | chip3: development? [ 11 | 'webpack-dev-server/client?http://localhost:8080/', 12 | 'webpack/hot/only-dev-server', 13 | 'react-hot-loader/patch', 14 | 'babel-polyfill', 15 | d('src/index.js'), 16 | ] : [ 17 | 'babel-polyfill', 18 | d('src/index.js'), 19 | ], 20 | }, 21 | 22 | devtool: development 23 | ? "cheap-module-source-map" 24 | : "hidden-source-map", 25 | 26 | resolve: { 27 | extensions: [ '.js', '.jsx', '.json' ], 28 | modules: [ 'node_modules', d('./src') ], 29 | }, 30 | 31 | module: { 32 | loaders: [{ 33 | test: /\.jsx?$/, 34 | include: d('src'), 35 | loaders: ['babel-loader'], 36 | }, { 37 | test: /\.json$/, 38 | loaders: ['json-loader'], 39 | }, { 40 | test: /\.(png|svg|html|css)$/, 41 | include: d('src/Workbench'), 42 | loaders: [ 43 | 'file-loader?context=./src/Workbench&name=[name].[ext]?[hash:10]', 44 | ] 45 | }] 46 | }, 47 | 48 | node: { 49 | fs: "empty" 50 | }, 51 | 52 | devServer: { 53 | noInfo: false, 54 | hot: development, 55 | historyApiFallback: true, 56 | contentBase: d('dist'), 57 | publicPath: 'http://localhost:8080/', 58 | stats: { chunks: false }, 59 | }, 60 | 61 | output: { 62 | path: d('dist'), 63 | pathinfo: development, 64 | filename: '[name].js', 65 | library: 'Chip3', 66 | libraryTarget: 'umd', 67 | publicPath: development 68 | ? 'http://localhost:8080/' 69 | : '/', 70 | }, 71 | 72 | plugins: [ 73 | new webpack.NoErrorsPlugin(), 74 | new webpack.EnvironmentPlugin(['NODE_ENV']), 75 | new webpack.LoaderOptionsPlugin({ 76 | debug: development, 77 | minimize: !false, 78 | }), 79 | ].concat(development? [ 80 | new webpack.HotModuleReplacementPlugin(), 81 | new WebpackShellPlugin({ 82 | 'onBuildStart': "open 'http://localhost:8080'", 83 | }), 84 | ] : [ 85 | new webpack.optimize.UglifyJsPlugin({ 86 | comments: /\/\/#/, // Keep only source maps 87 | compress: { warnings: false }, 88 | }), 89 | ]), 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015, zenoamaro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEVELOPMENT=env NODE_ENV=development 2 | PRODUCTION=env NODE_ENV=production 3 | NODE=./node_modules/.bin/babel-node 4 | LINT=./node_modules/.bin/eslint 5 | TEST=./node_modules/.bin/mocha 6 | TEST_DIRECT=./node_modules/.bin/_mocha 7 | TEST_COMPILERS=js:babel/register 8 | DOCS=./node_modules/.bin/jsdoc 9 | COMPILE=./node_modules/.bin/babel 10 | BUILD=./node_modules/.bin/webpack 11 | DEV_SERVER=./node_modules/.bin/webpack-dev-server 12 | 13 | usage: 14 | @echo PACKAGE 15 | @echo - prepublish .... tests, rebuilds lib, dist and docs. 16 | @echo - build ......... builds and optimizes distributables. 17 | @echo - devel ......... rebuilds on file change. 18 | @echo - clean ......... removes the built artifacts. 19 | @echo 20 | @echo META 21 | @echo - docs .......... compiles the docs from the sources. 22 | @echo - lint .......... lints the source. 23 | @echo - test .......... runs the unit tests. 24 | @echo - test-watch .... reruns the unit tests on file change. 25 | 26 | prepublish: clean lint test docs compile build 27 | 28 | clean: 29 | @rm -rf dist lib docs 30 | 31 | devel: 32 | @$(DEVELOPMENT) $(DEV_SERVER) --config .webpackrc 33 | 34 | lint: 35 | @$(LINT) src 36 | 37 | compile: 38 | @$(COMPILE) -q -d lib src 39 | 40 | build: clean 41 | @$(PRODUCTION) $(BUILD) --config .webpackrc 42 | 43 | test: 44 | @$(TEST) --compilers $(TEST_COMPILERS) test/index.js 45 | 46 | test-watch: 47 | @$(TEST) -bw -R min --compilers $(TEST_COMPILERS) test/index.js 48 | 49 | docs: 50 | -@$(DOCS) --configure .jsdocrc 51 | 52 | .PHONY: usage prepublish clean devel\ 53 | build docs lint test test-watch 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chip3 2 | ===== 3 | 4 | Chip3 is an interactive simulator of a simple computer. 5 | 6 | It simulates a Von Neumann 8-bit CPU with a 3-bit instruction set, a 5-bit memory addressing scheme, and one register. 7 | 8 | Included is a workbench with inspectors to examine the state of the whole system down to clock cycle level, and supports time-travel to previous states aiding learning and debugging. The architecture is simple, declarative, and is separated between system and inspector. The code itself is as simple as possible, and is laid out in a purely functional fashion. 9 | 10 | [Try the live demo](https://zenoamaro.github.io/chip3). 11 | 12 | 1. [Quick start](#quick-start) 13 | 2. [Running programs](#running-programs) 14 | 3. [Programmer's handbook](#programmers-handbook) 15 | 4. [API reference](#api-reference) 16 | 5. [Building and testing](#building-and-testing) 17 | 6. [Compatibility](#compatibility) 18 | 7. [Roadmap](#roadmap) 19 | 8. [Changelog](#changelog) 20 | 9. [License](#license) 21 | 22 | 23 | Quick start 24 | ----------- 25 | [Try the live demo](https://zenoamaro.github.io/chip3) to give the system a spin. Altough there is no way to input a custom program in the _Workbench_ at the moment, the machine comes preloaded with a sample "bouncy bits" program that can be fully stepped through and will output to console. 26 | 27 | ### What can I do with it? 28 | 29 | The Workbench will initialize a pristine system with a program already loaded into memory and ready to run. 30 | 31 | You can step through and fully inspect the system at every clock cycle, even mid-phase, and rewind execution to better understand the effects of the code on CPU and memory. 32 | 33 | The Workbench is not yet feature-complete, but you can code and run your own programs as detailed under [Running programs](#running-programs). 34 | 35 | For a more detailed explanation of how the system is composed, how the CPU works and what opcodes are available, see the [Programmer's handbook](#programmers-handbook). 36 | 37 | 38 | Running programs 39 | ---------------- 40 | To run your own programs, the easiest way is to clone the repository, build a runnable version, and edit the resulting `dist/index.html` replacing the provided program with your own. 41 | 42 | git clone https://github.com/zenoamaro/chip3.git && cd chip3 43 | npm install 44 | make build 45 | open dist/index.html 46 | 47 | Another way is to install the package locally, and instance the workbench manually: 48 | 49 | npm install zenoamaro/chip3 50 | 51 | ~~~js 52 | import {Workbench} from 'chip3'; 53 | 54 | Workbench.App.bootstrap(document.body, { 55 | program: [ 56 | /* 57 | Bouncy bits 58 | ----------- 59 | This program bounces a single bit inside the accumulator to the 60 | left and to the right, changing direction whenever it hits the 61 | bounds of the register. 62 | */ 63 | 64 | /* 00000 */ 0b00110001, // init: LD 65 | /* 00001 */ 0b11100000, // output: OUT 66 | /* 00010 */ 0b10010010, // bounds: AND 67 | /* 00011 */ 0b11000111, // JZ [move] 68 | /* 00100 */ 0b00110000, // reverse: LD 69 | /* 00101 */ 0b00000010, // NOT 70 | /* 00110 */ 0b01010000, // ST 71 | /* 00111 */ 0b00110000, // move: LD 72 | /* 01000 */ 0b11001100, // JZ [right] 73 | /* 01001 */ 0b00110001, // left: LD 74 | /* 01010 */ 0b00001000, // ROL 75 | /* 01011 */ 0b10101110, // JMP [store] 76 | /* 01100 */ 0b00110001, // right: LD 77 | /* 01101 */ 0b00010000, // ROR 78 | /* 01110 */ 0b01010001, // store: ST 79 | /* 01111 */ 0b10100001, // JMP output 80 | /* 10000 */ 0b11111111, // data: DATA 255 81 | /* 10001 */ 0b10000000, // DATA 128 82 | /* 10010 */ 0b10000001, // DATA 129 83 | ] 84 | }); 85 | ~~~ 86 | 87 | 88 | Programmer's handbook 89 | ----------- 90 | Chip3 simulates a simple yet complete computer with the following features: 91 | 92 | - a Von Neumann architecture, where code and data share the same address space and memory; code *is* data, so code is allowed to modify itself or be interchangeably interpreted as data. 93 | 94 | - a 3-bit instruction set, giving a grand total of eight available instructions. 95 | 96 | - a 5-bit addressing scheme allowing access to thirty-two 8-bit words of glorious memory. 97 | 98 | - a single general-purpose 8-bit register named *accumulator*. 99 | 100 | - internal registers to store current instruction, addressing, program counter, and to interface to the bus. 101 | 102 | The CPU has a strict, non-pipelined instruction cycle consisting of: 103 | 104 | 1. **the FETCH phase (always two clock cycles)** 105 | During this phase, memory is asked for the contents of the location at _program counter_ on the bus. The results are read and decoded on the next cycle. 106 | 107 | 2. **the EXECUTION phase (up to two clock cycles)** 108 | The instruction is executed according to the contents of the registers, and will require one or two cycles depending on whether there is a memory read involved. Writing to memory requires only one cycle. 109 | 110 | The following table describes the current instruction set: 111 | 112 | | Opcode | Mnemonic | Operands | T | Function | 113 | |--------|----------|----------|---|-------------------------------------| 114 | | 000 | OPR | operator | 1 | Operate on accumulator | 115 | | 001 | LD | address | 2 | Load memory into accumulator | 116 | | 010 | ST | address | 1 | Store accumulator into memory | 117 | | 011 | ADD | address | 2 | Add memory location to accumulator | 118 | | 100 | AND | address | 2 | Bitwise AND memory with accumulator | 119 | | 101 | JMP | address | 1 | Inconditional jump | 120 | | 110 | JZ | address | 1 | Jump if accumulator is zero | 121 | | 111 | OUT | | 1 | Output value of accumulator | 122 | 123 | The `operator` type of operand is a set of flags specifying operations to be run on the accumulator. Every operation whose flag is set will run, strictly in the order presented below. Zero flags set are practically equivalent to a `NOP` instruction, and are in fact disassembled as such. 124 | 125 | | Operator | Mnemonic | Function | 126 | |----------|----------|------------------------------| 127 | | 00001 | CLR | Clear accumulator | 128 | | 00010 | NOT | One's complement accumulator | 129 | | 00100 | INC | Increment accumulator | 130 | | 01000 | ROL | Rotate accumulator left | 131 | | 10000 | ROR | Rotate accumulator right | 132 | 133 | 134 | API reference 135 | ------------- 136 | [See the source-generated documentation](docs/index.html). 137 | 138 | 139 | Building and testing 140 | -------------------- 141 | To test or work on the project, clone it and install dependencies: 142 | 143 | git clone https://github.com/zenoamaro/chip3.git 144 | npm install 145 | 146 | You can run the automated test suite like so (altough there are no tests yet): 147 | 148 | npm test 149 | 150 | And build an ES5 and a distributable version of the source: 151 | 152 | make build 153 | 154 | A quick worflow for development, which reloads on every file change: 155 | 156 | make devel 157 | 158 | More tasks are available on the [Makefile](Makefile): 159 | 160 | prepublish .... tests, rebuilds lib, dist and docs. 161 | build ......... builds and optimizes distributables. 162 | devel ......... rebuilds on file change. 163 | clean ......... removes the built artifacts. 164 | docs .......... compiles the docs from the sources. 165 | lint .......... lints the source. 166 | test .......... runs the unit tests. 167 | test-watch .... reruns the unit tests on file change. 168 | 169 | 170 | Roadmap 171 | ------- 172 | - [ ] A better bus implementation to move data around 173 | - [ ] The ability to modify ram and cpu state in-place 174 | - [ ] A better disassembler 175 | - [ ] A working assembler 176 | - [ ] Multiple CPU families or systems 177 | - [ ] Proper I/O and devices 178 | 179 | 180 | License 181 | ------- 182 | The MIT License (MIT) 183 | 184 | Copyright (c) 2015, zenoamaro 185 | 186 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 187 | 188 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 189 | 190 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chip3 Workbench 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 |
Loading...
20 | 21 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chip3", 3 | "version": "0.1.0", 4 | "description": "An interactive simulator of a simple computer", 5 | "homepage": "https://github.com/zenoamaro/chip3", 6 | "license": "MIT", 7 | "private": true, 8 | "keywords": [ 9 | "simulator", 10 | "emulator", 11 | "chip", 12 | "cpu", 13 | "ram", 14 | "learn", 15 | "learning" 16 | ], 17 | "author": "zenoamaro ", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/zenoamaro/chip3.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/zenoamaro/chip3/issues", 24 | "email": "zenoamaro " 25 | }, 26 | "main": "lib/index.js", 27 | "scripts": { 28 | "build": "make build", 29 | "devel": "make devel", 30 | "docs": "make docs", 31 | "test": "make test", 32 | "clean": "make clean" 33 | }, 34 | "dependencies": { 35 | "react": "^15.0.2", 36 | "react-dom": "^15.0.2", 37 | "react-hot-loader": "^3.0.0-beta.2", 38 | "should": "^8.4.0" 39 | }, 40 | "devDependencies": { 41 | "babel-cli": "^6.8.0", 42 | "babel-eslint": "^6.0.4", 43 | "babel-loader": "^6.2.10", 44 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 45 | "babel-polyfill": "^6.20.0", 46 | "babel-preset-es2015": "^6.18.0", 47 | "babel-preset-react": "^6.5.0", 48 | "babel-preset-stage-0": "^6.5.0", 49 | "eslint": "^2.10.1", 50 | "eslint-plugin-babel": "^3.2.0", 51 | "eslint-plugin-react": "^5.1.1", 52 | "file-loader": "^0.8.5", 53 | "jsdoc": "^3.4.0", 54 | "jsdoc-babel": "^0.1.0", 55 | "mocha": "^2.5.3", 56 | "should": "^7.0.2", 57 | "webpack": "^2.2.0-rc.3", 58 | "webpack-dev-server": "^2.2.0-rc.0", 59 | "webpack-shell-plugin": "^0.4.2" 60 | }, 61 | "files": [ 62 | "lib", 63 | "LICENSE", 64 | "README.md" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/Assembler/Assembler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Assembles an AST into a stream of opcodes. 3 | * 4 | * @module Assembler/Assembler 5 | */ 6 | 7 | import {parseSource} from './Parser'; 8 | import {dereference} from './utils'; 9 | import {validateOperandType} from './utils'; 10 | import * as instructions from './instructions'; 11 | 12 | 13 | /** 14 | * Performs the first pass of compilation over an abstract syntax. 15 | * 16 | * Will validate instruction types and operands, detect duplicated labels, 17 | * assign memory addresses to each instruction for later use in linking. 18 | * 19 | * @param {AST} ast 20 | * @returns {AST} 21 | */ 22 | export function compile(ast) { 23 | let addr = 0; // Memory address of next instruction 24 | const labels = {}; // Encountered labels 25 | 26 | return ast.map(instr => { 27 | const {label, operands} = instr; 28 | const type = instr.type.toUpperCase(); 29 | const instrType = instructions[type]; 30 | const instrOperands = instrType.operands; 31 | 32 | if (label) { 33 | if (label in labels) { 34 | throw new Error(`Duplicate label, '${label}'`); 35 | } else { 36 | labels[label] = true; 37 | } 38 | } 39 | 40 | if (!instrType) { 41 | throw new Error(`Unknown instruction, '${instrType}'`); 42 | } 43 | 44 | // There are no optional operands whatsoever 45 | // so we can simply match the length of the signature 46 | if (operands.length !== instrOperands.length) { 47 | throw new Error(` 48 | Instruction '${instrType.name}' expected ${instrOperands.length} 49 | operands, found ${operands.length} instead 50 | `); 51 | } 52 | 53 | // Compare the signature of the operands with 54 | // the expected signature of the instruction 55 | operands.forEach((op, i) => { 56 | const opType = instrOperands[i]; 57 | if (!validateOperandType(op, opType)) { 58 | throw new Error(` 59 | Instruction '${instrType.name}' expected operand ${i} to be of type 60 | '${opType}', found '${op.type}' instead 61 | `); 62 | } 63 | }); 64 | 65 | const size = instrType.size(instr); 66 | instr = {...instr, type, size, addr}; 67 | addr += size; // Advance by size of opcode 68 | return instr; 69 | }); 70 | } 71 | 72 | /** 73 | * Performs linking on compiled abstract syntax. 74 | * 75 | * Every operand of type `address` is dereferenced to the actual location 76 | * in memory it is pointing to. 77 | * 78 | * @param {AST} ast 79 | * @returns {AST} 80 | */ 81 | export function link(ast) { 82 | return ast.map(instr => { 83 | const operands = instr.operands.map(op => { 84 | // Only `address` type operands need dereferencing. 85 | if (op.type === 'address') { 86 | const label = op.value; 87 | const location = dereference(ast, label); 88 | if (!location) throw new Error(`Unknown reference, '${label}'`); 89 | return {...op, addr:location.addr}; 90 | } else { 91 | return op; 92 | } 93 | }); 94 | 95 | return { 96 | ...instr, 97 | operands, 98 | }; 99 | }); 100 | } 101 | 102 | /** 103 | * Assembles a linked abstract syntax into a list of opcodes. 104 | * 105 | * @param {AST} ast 106 | * @returns {Program} 107 | */ 108 | export function assemble(ast) { 109 | return ast.reduce((program, instr) => { 110 | const type = instructions[instr.type]; 111 | const opcodes = type.assemble(instr); 112 | return [...program, ...opcodes]; 113 | }, []); 114 | } 115 | 116 | /** 117 | * Shortcut for perform both parsing and assembling in one step. 118 | * 119 | * @param {String} source 120 | * @returns {Program} 121 | */ 122 | export function assembleSource(source) { 123 | const stream = parseSource(source); 124 | const ast = link(compile(stream)); 125 | return assemble(ast); 126 | } 127 | -------------------------------------------------------------------------------- /src/Assembler/Parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses source code into abstract syntax. 3 | * 4 | * @module Assembler/Parser 5 | */ 6 | 7 | 8 | /** 9 | * Registered token types. 10 | * 11 | * @type {TokenType[]} 12 | */ 13 | const tokenTypes = [ 14 | {name:'eol', skip:false, pattern:/^\n+/}, 15 | {name:'whitespace', skip:true, pattern:/^\s+/}, 16 | {name:'number', skip:false, pattern:/^(\d+)/}, 17 | {name:'string', skip:false, pattern:/^"((?:[^"\\]|\\.)*)"/}, 18 | {name:'address', skip:false, pattern:/^\[([a-zA-Z_][\w]*)\]/}, 19 | {name:'label', skip:false, pattern:/^([a-zA-Z_][\w]*):/}, 20 | {name:'identifier', skip:false, pattern:/^([a-zA-Z_][\w]*)/}, 21 | {name:'comment', skip:true, pattern:/^;.*/}, 22 | ]; 23 | 24 | /** 25 | * Finds the first token type that matches the beginning of the source. 26 | * 27 | * @param {String} source 28 | * @returns {Token?} 29 | */ 30 | export function lexToken(source) { 31 | const type = tokenTypes.find(type => type.pattern.test(source)); 32 | if (!type) throw new Error(`Invalid expression '${source.slice(0, 20)}...'`); 33 | const [literal, value] = type.pattern.exec(source); 34 | return {type:type.name, literal, value, skip:type.skip}; 35 | } 36 | 37 | /** 38 | * Lexes source code into a list of tokens. 39 | * 40 | * @param {String} source 41 | * @returns {Token[]} 42 | */ 43 | export function lex(source) { 44 | const tokens = []; 45 | while (source.length) { 46 | const token = lexToken(source); 47 | source = source.slice(token.literal.length); 48 | if (!token.skip) tokens.push(token); 49 | } 50 | return tokens; 51 | } 52 | 53 | /** 54 | * Parses a list of tokens into a list of instructions. 55 | * 56 | * Each line of source code may contain a label, an instruction followed by 57 | * zero or more operands, and a comment, in this order. 58 | * 59 | * Returns a list of instructions, one for each line, as abstract syntax. 60 | * 61 | * @param {Token[]} tokens 62 | * @returns {AST} 63 | */ 64 | export function parse(tokens) { 65 | // Chunk tokens into blocks of code describing 66 | // the same operation (ie. separated by EOL) 67 | const blocks = tokens.reduce((blocks, token) => { 68 | if (token.type === 'eol') { 69 | return [...blocks, []]; 70 | } else { 71 | blocks[blocks.length-1].push(token); 72 | return blocks; 73 | } 74 | }, [[]]).filter(b => b.length); 75 | 76 | return blocks.map(block => { 77 | block = [...block]; 78 | const instr = {}; 79 | 80 | // Labels can only occur in first position 81 | if (block[0].type === 'label') { 82 | instr.label = block[0].value; 83 | block.shift(); 84 | } 85 | 86 | // First or second position must be identifier 87 | // and is always treated as the instruction 88 | if (block[0].type === 'identifier') { 89 | instr.type = block[0].value; 90 | block.shift(); 91 | } else { 92 | throw new Error(`Expected instruction, found '${block.value}'`); 93 | } 94 | 95 | // Operands take the rest of the block 96 | instr.operands = block; 97 | return instr; 98 | }); 99 | } 100 | 101 | /** 102 | * Shortcut for perform both lexing and parsing in one step. 103 | * 104 | * @param {String} source 105 | * @returns {AST} 106 | */ 107 | export function parseSource(source) { 108 | return parse(lex(source)); 109 | } 110 | -------------------------------------------------------------------------------- /src/Assembler/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Assembles source code into Chip3 machine code. 3 | * 4 | * @module Assembler 5 | */ 6 | 7 | export * as Assembler from './Assembler'; 8 | export * as Parser from './Parser'; 9 | -------------------------------------------------------------------------------- /src/Assembler/instructions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Instruction set 3 | * 4 | * @module Assembler/instructions 5 | */ 6 | 7 | import {assemble} from './utils'; 8 | import {int} from './utils'; 9 | import {string} from './utils'; 10 | 11 | 12 | /** 13 | * Store data into memory 14 | * @type {Instruction} 15 | */ 16 | export const DB = { 17 | name: 'DB', 18 | operands: ['address|number|string'], 19 | size: instr => ( 20 | DB.assemble(instr).length 21 | ), 22 | assemble: instr => { 23 | const arg = instr.operands[0]; 24 | switch (arg.type) { 25 | case 'address': return [int(arg.addr)]; 26 | case 'number': return [int(arg.value)]; 27 | case 'string': return string(arg.value); 28 | } 29 | }, 30 | }; 31 | 32 | // TODO: Combine OPRs into one opcode 33 | 34 | /** 35 | * Perform no operation 36 | * @type {Instruction} 37 | */ 38 | export const NOP = { 39 | name: 'NOP', 40 | operands: [], 41 | size: instr => 1, 42 | assemble: instr => [assemble(0b000, 0b00000)], 43 | }; 44 | 45 | /** 46 | * Clear the accumulator 47 | * @type {Instruction} 48 | */ 49 | export const CLR = { 50 | name: 'CLR', 51 | operands: [], 52 | size: instr => 1, 53 | assemble: instr => [assemble(0b000, 0b00001)], 54 | }; 55 | 56 | /** 57 | * Bitwise negate the accumulator 58 | * @type {Instruction} 59 | */ 60 | export const NOT = { 61 | name: 'NOT', 62 | operands: [], 63 | size: instr => 1, 64 | assemble: instr => [assemble(0b000, 0b00010)], 65 | }; 66 | 67 | /** 68 | * Increase the accumulator by one 69 | * @type {Instruction} 70 | */ 71 | export const INC = { 72 | name: 'INC', 73 | operands: [], 74 | size: instr => 1, 75 | assemble: instr => [assemble(0b000, 0b00100)], 76 | }; 77 | 78 | /** 79 | * Rotate the accumulator left by one 80 | * @type {Instruction} 81 | */ 82 | export const ROL = { 83 | name: 'ROL', 84 | operands: [], 85 | size: instr => 1, 86 | assemble: instr => [assemble(0b000, 0b01000)], 87 | }; 88 | 89 | /** 90 | * Rotate the accumulator right by one 91 | * @type {Instruction} 92 | */ 93 | export const ROR = { 94 | name: 'ROR', 95 | operands: [], 96 | size: instr => 1, 97 | assemble: instr => [assemble(0b000, 0b10000)], 98 | }; 99 | 100 | /** 101 | * Load memory into accumulator 102 | * @type {Instruction} 103 | */ 104 | export const LD = { 105 | name: 'LD', 106 | operands: ['address'], 107 | size: instr => 1, 108 | assemble: instr => { 109 | const arg = instr.operands[0]; 110 | if (arg.addr === 0) { 111 | throw new Error('Loading address `zero`, use LDA instead'); 112 | } 113 | return [assemble(0b001, arg.addr)]; 114 | }, 115 | }; 116 | 117 | /** 118 | * Load memory at accumulator into accumulator 119 | * @type {Instruction} 120 | */ 121 | export const LDA = { 122 | name: 'LDA', 123 | operands: [], 124 | size: instr => 1, 125 | assemble: instr => [assemble(0b001, 0b00000)], 126 | }; 127 | 128 | /** 129 | * Store accumulator into memory 130 | * @type {Instruction} 131 | */ 132 | export const ST = { 133 | name: 'ST', 134 | operands: ['address'], 135 | size: instr => 1, 136 | assemble: instr => { 137 | const arg = instr.operands[0]; 138 | if (arg.addr === 0) { 139 | throw new Error('Loading address `zero`, use LDA instead'); 140 | } 141 | return [assemble(0b010, arg.addr)]; 142 | }, 143 | }; 144 | 145 | /** 146 | * Store accumulator into last read address 147 | * @type {Instruction} 148 | */ 149 | export const STA = { 150 | name: 'STA', 151 | operands: [], 152 | size: instr => 1, 153 | assemble: instr => [assemble(0b010, 0b00000)], 154 | }; 155 | 156 | /** 157 | * Add memory to accumulator 158 | * @type {Instruction} 159 | */ 160 | export const ADD = { 161 | name: 'ADD', 162 | operands: ['address'], 163 | size: instr => 1, 164 | assemble: instr => { 165 | const arg = instr.operands[0]; 166 | return [assemble(0b011, arg.addr)]; 167 | }, 168 | }; 169 | 170 | /** 171 | * Bitwise and accumulator with memory 172 | * @type {Instruction} 173 | */ 174 | export const AND = { 175 | name: 'AND', 176 | operands: ['address'], 177 | size: instr => 1, 178 | assemble: instr => { 179 | const arg = instr.operands[0]; 180 | return [assemble(0b100, arg.addr)]; 181 | }, 182 | }; 183 | 184 | /** 185 | * Unconditionally jump to address 186 | * @type {Instruction} 187 | */ 188 | export const JMP = { 189 | name: 'JMP', 190 | operands: ['address'], 191 | size: instr => 1, 192 | assemble: instr => { 193 | const arg = instr.operands[0]; 194 | return [assemble(0b101, arg.addr)]; 195 | }, 196 | }; 197 | 198 | /** 199 | * Jump to address if accumulator is zero 200 | * @type {Instruction} 201 | */ 202 | export const JZ = { 203 | name: 'JZ', 204 | operands: ['address'], 205 | size: instr => 1, 206 | assemble: instr => { 207 | const arg = instr.operands[0]; 208 | return [assemble(0b110, arg.addr)]; 209 | }, 210 | }; 211 | 212 | /** 213 | * Output accumulator 214 | * @type {Instruction} 215 | */ 216 | export const OUT = { 217 | name: 'OUT', 218 | operands: [], 219 | size: instr => 1, 220 | assemble: instr => [assemble(0b111)], 221 | }; 222 | -------------------------------------------------------------------------------- /src/Assembler/types.jsdoc: -------------------------------------------------------------------------------- 1 | /** 2 | * Assembler type definitions. 3 | * 4 | * @module Assembler/types 5 | */ 6 | 7 | /** 8 | * A type of token. 9 | * 10 | * @typedef {Object} TokenType 11 | * @property {String} name 12 | * @property {RegExp} pattern 13 | */ 14 | 15 | /** 16 | * A token. 17 | * 18 | * @typedef {Object} Token 19 | * @property {String} type 20 | * @property {Any} value 21 | */ 22 | 23 | /** 24 | * A type of instruction. 25 | * 26 | * @typedef {Object} InstructionType 27 | * @property {String} name 28 | * @property {String[]} operands 29 | * @property {Function} assemble 30 | */ 31 | 32 | /** 33 | * An instruction. 34 | * 35 | * @typedef {Object} Instruction 36 | * @property {String} type 37 | * @property {Token[]} operands 38 | * @property {String} [label] 39 | */ 40 | 41 | /** 42 | * A syntax tree. 43 | * 44 | * @typedef {Instruction[]} AST 45 | */ 46 | 47 | /** 48 | * Codifies an instruction with operands. 49 | * 50 | * In Chip3 this will always be exactly one byte. 51 | * 52 | * @typedef {Number} Opcode 53 | */ 54 | 55 | /** 56 | * A list of opcodes. 57 | * 58 | * @typedef {Opcode[]} Program 59 | */ 60 | -------------------------------------------------------------------------------- /src/Assembler/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Assembler-related utils 3 | * 4 | * @module Assembler/utils 5 | */ 6 | 7 | 8 | /** 9 | * Shortcut to parse integers from text, with fallback. 10 | * 11 | * @param {String} n 12 | * @param {Number} def 13 | * @returns {Number} 14 | */ 15 | export function int(n, def=0) { 16 | const result = parseInt(n, 10); 17 | return !isNaN(result) ? result : def; 18 | } 19 | 20 | /** 21 | * Shortcut to convert a string-like to a null-terminated string array. 22 | * 23 | * @param {String} s 24 | * @returns {String[]} 25 | */ 26 | export function string(s) { 27 | return String(s).split('') 28 | .map(c => c.charCodeAt(0)) 29 | .concat(0); 30 | } 31 | 32 | /** 33 | * Finds the location of a label in an abstract syntax. 34 | * Returns the operation that was labeled as such. 35 | * 36 | * @param {AST} ast 37 | * @param {String} label 38 | * @returns {Operation} 39 | */ 40 | export function dereference(ast, label) { 41 | return ast.find(op => op.label === label); 42 | } 43 | 44 | /** 45 | * Checks if the given operand matches type. 46 | * 47 | * Type may be a single type, such as `"label"`, or an union of types, like 48 | * `"number|string"`. 49 | * 50 | * @param {Token} operand 51 | * @param {String} type 52 | * @returns {Boolean} 53 | */ 54 | export function validateOperandType(operand, type) { 55 | return type.split('|').includes(operand.type); 56 | } 57 | 58 | /** 59 | * Assembles an instruction and an operand into a complete 8-bit opcode. 60 | * 61 | * @param {Number} instr 62 | * @param {Number} operand 63 | * @returns {Number} 64 | */ 65 | export function assemble(instr, operand=0) { 66 | return ( 67 | /* eslint-disable no-bitwise */ 68 | ((int(instr) & 0b111) << 5) + // Upper 3 bits 69 | (int(operand) & 0b11111) // Lower 5 bits 70 | /* eslint-enable no-bitwise */ 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/System/CPU.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions to create and operate on CPUs. 3 | * 4 | * @module System/CPU 5 | */ 6 | 7 | 8 | /** 9 | * A CPU in a virgin state. 10 | * 11 | * @typedef {Device} CPU 12 | * @property {String} phase - Current phase of execution 13 | * @property {String} next - Next phase to be executed 14 | * @property {Boolean} rst - Reset flag 15 | * @property {Boolean} read - Read request flag 16 | * @property {Boolean} write - Write request flag 17 | * @property {Boolean} output - Device output request 18 | * @property {Number} a - Accumulator 19 | * @property {Number} dr - Data register 20 | * @property {Number} ir - Instruction register 21 | * @property {Number} ar - Address register 22 | * @property {Number} or - Output register 23 | * @property {Number} lr - Last load address register 24 | * @property {Number} pc - Program counter 25 | */ 26 | 27 | 28 | /** 29 | * Handlers for every phase the CPU can transition to. 30 | */ 31 | export const phases = { 32 | HALT, FETCH, FETCH2, 33 | OPR, CLR, NOT, INC, ROL, ROR, 34 | LD, LD2, ST, 35 | ADD, ADD2, 36 | AND, AND2, 37 | JMP, JZ, 38 | OUT, 39 | }; 40 | 41 | /** 42 | * A description of an opcode. 43 | * 44 | * @typedef {Object} Opcode 45 | * @property {Number} code - Unique code representing this opcode 46 | * @property {String} mnemonic - Next phase to be executed 47 | * @property {String} phase - CPU phase that handles this opcode 48 | * @property {Array} operands - Data types for all accepted operands 49 | */ 50 | 51 | /** 52 | * Specifications for all opcodes supported by the CPU. 53 | * 54 | * | Opcode | Mnemonic | Operands | T | Function | 55 | * |--------|----------|----------|---|-------------------------------------| 56 | * | 000 | OPR | operator | 1 | Operate on accumulator | 57 | * | 001 | LD | address | 2 | Load memory into accumulator | 58 | * | 010 | ST | address | 1 | Store accumulator into memory | 59 | * | 011 | ADD | address | 2 | Add memory location to accumulator | 60 | * | 100 | AND | address | 2 | Bitwise AND memory with accumulator | 61 | * | 101 | JMP | address | 1 | Inconditional jump | 62 | * | 110 | JZ | address | 1 | Jump if accumulator is zero | 63 | * | 111 | OUT | | 1 | Output accumulator | 64 | */ 65 | export const opcodes = [ 66 | {code:0b000, mnemonic:'OPR', phase:'OPR', operands:['operator']}, 67 | {code:0b001, mnemonic:'LD', phase:'LD', operands:['address']}, 68 | {code:0b010, mnemonic:'ST', phase:'ST', operands:['address']}, 69 | {code:0b011, mnemonic:'ADD', phase:'ADD', operands:['address']}, 70 | {code:0b100, mnemonic:'AND', phase:'AND', operands:['address']}, 71 | {code:0b101, mnemonic:'JMP', phase:'JMP', operands:['address']}, 72 | {code:0b110, mnemonic:'JZ', phase:'JZ', operands:['address']}, 73 | {code:0b111, mnemonic:'OUT', phase:'OUT', operands:[]}, 74 | ]; 75 | 76 | /** 77 | * A description of an operator. 78 | * 79 | * @typedef {Object} Opcode 80 | * @property {Number} code - Flag activating this operator 81 | * @property {String} mnemonic - Next phase to be executed 82 | * @property {String} phase - CPU phase that handles this operator 83 | */ 84 | 85 | /** 86 | * Specifications for all opcodes supported by the CPU. 87 | * 88 | * All operators whose flag is expressed will run in the same cycle and 89 | * in the same order as they are presented here. 90 | * 91 | * | Operator | Mnemonic | Function | 92 | * |----------|----------|------------------------------| 93 | * | 00001 | CLR | Clear accumulator | 94 | * | 00010 | NOT | One's complement accumulator | 95 | * | 00100 | INC | Increment accumulator | 96 | * | 01000 | ROL | Rotate accumulator left | 97 | * | 10000 | ROR | Rotate accumulator right | 98 | */ 99 | export const operators = [ 100 | {code:0b00001, mnemonic:'CLR', phase:'CLR'}, 101 | {code:0b00010, mnemonic:'NOT', phase:'NOT'}, 102 | {code:0b00100, mnemonic:'INC', phase:'INC'}, 103 | {code:0b01000, mnemonic:'ROL', phase:'ROL'}, 104 | {code:0b10000, mnemonic:'ROR', phase:'ROR'}, 105 | ]; 106 | 107 | /** 108 | * Create a CPU in a virgin state. 109 | * 110 | * @method create 111 | * @returns {CPU} 112 | */ 113 | export function create() { 114 | return { 115 | phase: 'RESET', 116 | next: 'FETCH', 117 | rst: false, 118 | read: false, 119 | write: false, 120 | output: false, 121 | a: 0, 122 | ir: 0, 123 | ar: 0, 124 | dr: 0, 125 | or: 0, 126 | pc: 0, 127 | }; 128 | } 129 | 130 | /** 131 | * Executes one CPU cycle, returning the new state. 132 | * 133 | * @param {CPU} state 134 | * @returns {CPU} 135 | */ 136 | export function cycle(state) { 137 | let receiver; 138 | const phase = state.next; 139 | switch (true) { 140 | case state.rst: receiver = create; break; 141 | case phase in phases: receiver = phases[phase]; break; 142 | default: return {...state}; 143 | } 144 | return {...state, phase, ...receiver(state)}; 145 | } 146 | 147 | /** 148 | * Decodes a word into into opcode, operand and phase transition. 149 | * 150 | * Produces values for the instruction and address register, and the cpu 151 | * phase to transition to in order to execute the opcode. 152 | * 153 | * @method decode 154 | * @param {Number} word 155 | * @returns {Object} 156 | */ 157 | export function decode(word) { 158 | /* eslint-disable no-bitwise */ 159 | const ir = (word & 0b11100000) >> 5; 160 | const ar = (word & 0b00011111) >> 0; 161 | /* eslint-enable no-bitwise */ 162 | const opcode = opcodes[ir]; 163 | return {ir, ar, opcode}; 164 | } 165 | 166 | /** 167 | * Decodes an operator into a list of operators. 168 | * 169 | * Multiple operators can be executed on the same cycle, and are 170 | * returned as a list of phases to handle them. Operations always execute 171 | * as has been specified (ascending order by code). 172 | * 173 | * @param {Number} opr 174 | * @returns {Array} 175 | */ 176 | export function decodeOperator(opr) { 177 | /* eslint-disable no-bitwise */ 178 | return operators.filter(o => opr & o.code); 179 | /* eslint-enable no-bitwise */ 180 | } 181 | 182 | /** 183 | * Keeps the machine halted until reset. 184 | * 185 | * @returns {CPU} 186 | */ 187 | export function HALT() { 188 | return { 189 | next: 'HALT', 190 | }; 191 | } 192 | 193 | /** 194 | * Fetch the instruction at PC. 195 | * 196 | * Points the address register to the program counter, raises the read 197 | * request flag, and waits for memory to reply. 198 | * 199 | * @param {CPU} state 200 | * @returns {CPU} 201 | */ 202 | export function FETCH(state) { 203 | return { 204 | ar: state.pc, 205 | read: true, 206 | write: false, 207 | output: false, 208 | next: 'FETCH2', 209 | }; 210 | } 211 | 212 | /** 213 | * Second step of the instruction fetch phase. 214 | * 215 | * Reads the value arriving from memory on the data register and writes 216 | * it into the instruction and address registers, then increments the 217 | * program counter. Will transition to the handler for the opcode 218 | * expressed by the instruction register. 219 | * 220 | * @param {CPU} state 221 | * @returns {CPU} 222 | */ 223 | export function FETCH2(state) { 224 | const {opcode, ir, ar} = decode(state.dr); 225 | const next = opcode.phase; 226 | return { 227 | ir, ar, 228 | read: false, 229 | pc: state.pc + 1, 230 | next, 231 | }; 232 | } 233 | 234 | /** 235 | * Load memory into accumulator. 236 | * 237 | * Points the address register to the memory location to be read, raises 238 | * the read request flag, and waits for memory to reply. When the address 239 | * is `zero`, the address in the accumulator is used instead. 240 | * 241 | * @param {CPU} state 242 | * @returns {CPU} 243 | */ 244 | function LD(state) { 245 | return { 246 | ar: state.ar || state.a, 247 | lr: state.ar, 248 | read: true, 249 | next: 'LD2', 250 | }; 251 | } 252 | 253 | /** 254 | * Second step of the load operation. 255 | * 256 | * Reads the value arriving from memory on the data register and writes 257 | * it into the accumulator. 258 | * 259 | * @param {CPU} state 260 | * @returns {CPU} 261 | */ 262 | function LD2(state) { 263 | return { 264 | a: state.dr, 265 | read: false, 266 | next: 'FETCH', 267 | }; 268 | } 269 | 270 | /** 271 | * Store accumulator into memory. 272 | * 273 | * Points the address register to the memory location to be written, 274 | * writes the contents of the accumulator on the data register, and 275 | * triggers the write request flag. When the address is `zero`, the 276 | * last read address is used instead. 277 | * 278 | * @param {CPU} state 279 | * @returns {CPU} 280 | */ 281 | function ST(state) { 282 | return { 283 | ar: state.ar || state.lr, 284 | dr: state.a, 285 | write: true, 286 | next: 'FETCH', 287 | }; 288 | } 289 | 290 | /** 291 | * Add memory location to accumulator. 292 | * 293 | * Points the address register to the memory location of the operand, 294 | * raises the read request flag, and waits for memory to reply. 295 | * 296 | * @param {CPU} state 297 | * @returns {CPU} 298 | */ 299 | function ADD(state) { 300 | return { 301 | ar: state.ar, 302 | read: true, 303 | next: 'ADD2', 304 | }; 305 | } 306 | 307 | /** 308 | * Second step of the addition operation. 309 | * 310 | * Reads the value arriving from memory on the data register, adds it to 311 | * the accumulator, and stores the results into the accumulator. 312 | * 313 | * @param {CPU} state 314 | * @returns {CPU} 315 | */ 316 | function ADD2(state) { 317 | return { 318 | a: (state.a + state.dr) % 256, 319 | read: false, 320 | next: 'FETCH', 321 | }; 322 | } 323 | 324 | /** 325 | * Bitwise AND memory with accumulator. 326 | * 327 | * Points the address register to the memory location of the operand, 328 | * raises the read request flag, and waits for memory to reply. 329 | * 330 | * @param {CPU} state 331 | * @returns {CPU} 332 | */ 333 | function AND(state) { 334 | return { 335 | read: true, 336 | next: 'AND2', 337 | }; 338 | } 339 | 340 | /** 341 | * Second step of the bitwise AND operation. 342 | * 343 | * Reads the value arriving from memory on the data register, ANDs it 344 | * with the accumulator, and stores the results into the accumulator. 345 | * 346 | * @param {CPU} state 347 | * @returns {CPU} 348 | */ 349 | function AND2(state) { 350 | return { 351 | /* eslint-disable no-bitwise */ 352 | a: (state.a & state.dr) % 256, 353 | /* eslint-enable no-bitwise */ 354 | read: false, 355 | next: 'FETCH', 356 | }; 357 | } 358 | 359 | /** 360 | * Inconditional jump. 361 | * 362 | * Points the program counter to the address in the address register. 363 | * If the instruction jumps to its own address, the machine will halt. 364 | * 365 | * @param {CPU} state 366 | * @returns {CPU} 367 | */ 368 | function JMP(state) { 369 | // Halt when jumping to same address. 370 | // Note that PC is already one after. 371 | if (state.ar === state.pc - 1) { 372 | return {next:'HALT', pc:state.ar}; 373 | } 374 | 375 | return { 376 | /* eslint-disable no-bitwise */ 377 | pc: state.ar & 0b00011111, 378 | /* eslint-enable no-bitwise */ 379 | next: 'FETCH', 380 | }; 381 | } 382 | 383 | /** 384 | * Jump if accumulator is zero. 385 | * 386 | * Points the program counter to the address in the address register if 387 | * the accumulator is zero. If a jump to the same address is attempted, 388 | * the machine will halt. 389 | * 390 | * @param {CPU} state 391 | * @returns {CPU} 392 | */ 393 | function JZ(state) { 394 | const z = state.a === 0; 395 | /* eslint-disable no-bitwise */ 396 | const ar = state.ar & 0b00011111; 397 | /* eslint-enable no-bitwise */ 398 | const pc = z? ar : state.pc; 399 | 400 | // Halt when jumping to same address. 401 | // Note that PC is already one after. 402 | if (pc === state.pc - 1) { 403 | return {next:'HALT', pc}; 404 | } 405 | 406 | return { 407 | pc, 408 | next: 'FETCH', 409 | }; 410 | } 411 | 412 | /** 413 | * Output accumulator. 414 | * 415 | * Outputs the value of the accumulator to the standard output. 416 | * 417 | * @param {CPU} state 418 | * @returns {CPU} 419 | */ 420 | function OUT(state) { 421 | return { 422 | next: 'FETCH', 423 | output: true, 424 | or: state.a, 425 | }; 426 | } 427 | 428 | /** 429 | * Executes operators over the accumulator. 430 | * 431 | * Executes the operation expressed by the address register on the 432 | * accumulator, and stores the results into the accumulator. Requires no 433 | * operands. 434 | * 435 | * @param {CPU} state 436 | * @returns {CPU} 437 | */ 438 | function OPR(state) { 439 | const ops = decodeOperator(state.ar); 440 | const newState = ops.reduce((partial, opr) => { 441 | const handler = phases[opr.phase]; 442 | const result = handler({...state, ...partial}); 443 | return {...partial, ...result}; 444 | }, {}); 445 | newState.next = 'FETCH'; 446 | return newState; 447 | } 448 | 449 | /** 450 | * Clear accumulator. 451 | * 452 | * Resets the accumulator to zero. 453 | * 454 | * @param {CPU} state 455 | * @returns {CPU} 456 | */ 457 | function CLR(state) { 458 | return { 459 | a: 0, 460 | }; 461 | } 462 | 463 | /** 464 | * One's complement accumulator. 465 | * 466 | * Replaces the value in the accumulator with its one's complement. 467 | * 468 | * @param {CPU} state 469 | * @returns {CPU} 470 | */ 471 | function NOT(state) { 472 | return { 473 | /* eslint-disable no-bitwise */ 474 | a: ~state.a & 0b11111111, 475 | /* eslint-enable no-bitwise */ 476 | }; 477 | } 478 | 479 | /** 480 | * Increment accumulator. 481 | * 482 | * Increments the value in the accumulator by 1. 483 | * 484 | * @param {CPU} state 485 | * @returns {CPU} 486 | */ 487 | function INC(state) { 488 | return { 489 | a: (state.a + 1) % 256, 490 | }; 491 | } 492 | 493 | /** 494 | * Rotate accumulator left. 495 | * 496 | * Rotates the accumulator to the left by 1. 497 | * 498 | * @param {CPU} state 499 | * @returns {CPU} 500 | */ 501 | function ROL(state) { 502 | return { 503 | /* eslint-disable no-bitwise */ 504 | a: (state.a << 1) % 256, 505 | /* eslint-enable no-bitwise */ 506 | }; 507 | } 508 | 509 | /** 510 | * Rotate accumulator right. 511 | * 512 | * Rotates the accumulator to the right by 1. 513 | * 514 | * @param {CPU} state 515 | * @returns {CPU} 516 | */ 517 | function ROR(state) { 518 | return { 519 | /* eslint-disable no-bitwise */ 520 | a: (state.a >> 1) % 256, 521 | /* eslint-enable no-bitwise */ 522 | }; 523 | } 524 | 525 | /** 526 | * Returns an overview of the state of a CPU as text. 527 | * 528 | * @param {CPU} state 529 | * @returns {String} 530 | */ 531 | export function toString(state) { 532 | const rst = state.rst? 'on' : 'off'; 533 | const read = state.read? 'on' : 'off'; 534 | const write = state.write? 'on' : 'off'; 535 | const {phase, next, a, dr, ir, ar, pc} = state; 536 | return `CPU ${phase}->${next} RST:${rst} READ:${read} WRITE:${write} ` 537 | + `A:${a} AR:${ar} DR:${dr} IR:${ir} PC:${pc}`; 538 | } 539 | -------------------------------------------------------------------------------- /src/System/Printer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions to create and operate on a printer. 3 | * 4 | * @module System/Printer 5 | */ 6 | 7 | 8 | /** 9 | * A printer in a virgin state. 10 | * 11 | * @typedef {Device} Printer 12 | * @property {Boolean} rst - Reset flag 13 | * @property {Boolean} output - Output request 14 | * @property {Number} or - Output value register 15 | * @property {Number[]} paper - The full printout 16 | */ 17 | 18 | 19 | /** 20 | * Create a printer in a virgin state. 21 | * 22 | * @method create 23 | * @returns {Printer} 24 | */ 25 | export function create() { 26 | return { 27 | output: false, 28 | or: 0, 29 | paper: [], 30 | }; 31 | } 32 | 33 | /** 34 | * Executes one printer cycle, returning the new state. 35 | * 36 | * @param {Printer} state 37 | * @returns {Printer} 38 | */ 39 | export function cycle(state) { 40 | let receiver; 41 | switch (true) { 42 | case state.rst: receiver = create; break; 43 | case state.output: receiver = print; break; 44 | default: return {...state}; 45 | } 46 | return { 47 | ...state, 48 | ...receiver(state), 49 | }; 50 | } 51 | 52 | /** 53 | * Pushes the value of the output register at the end of the paper, 54 | * thus simulating a print operation. 55 | * 56 | * @param {Printer} state 57 | * @returns {Printer} 58 | */ 59 | function print(state) { 60 | return { 61 | paper: [ 62 | ...state.paper, 63 | state.or, 64 | ], 65 | }; 66 | } 67 | 68 | /** 69 | * Returns an overview of the state of a printer as text. 70 | * 71 | * @param {Printer} state 72 | * @returns {String} 73 | */ 74 | export function toString(state) { 75 | const rst = state.rst? 'on' : 'off'; 76 | const output = state.output? 'on' : 'off'; 77 | return `Printer RST:${rst} output:${output} OR:${state.or}`; 78 | } 79 | -------------------------------------------------------------------------------- /src/System/RAM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions to create and operate on blocks of RAM. 3 | * 4 | * @module System/RAM 5 | */ 6 | 7 | 8 | /** 9 | * A block of random-access memory. 10 | * 11 | * @typedef {Device} RAM 12 | * @property {Boolean} rst - Reset flag 13 | * @property {Boolean} read - Read request flag 14 | * @property {Boolean} write - Write request flag 15 | * @property {Number} size - Size of memory in words 16 | * @property {Array} data - Memory locations 17 | * @property {Number} ar - Address register 18 | * @property {Number} dr - Data register 19 | */ 20 | 21 | 22 | /** 23 | * Create an empty RAM block. 24 | * 25 | * @method create 26 | * @returns {RAM} 27 | */ 28 | export function create() { 29 | return { 30 | rst: false, 31 | read: false, 32 | write: false, 33 | size: 0b100000, 34 | data: new Array(0b100000).fill(0), 35 | ar: 0, 36 | dr: 0, 37 | }; 38 | } 39 | 40 | /** 41 | * Executes one RAM cycle, returning the new state. 42 | * 43 | * @param {RAM} state 44 | * @returns {RAM} 45 | */ 46 | export function cycle(state) { 47 | let receiver; 48 | switch (true) { 49 | case state.rst: receiver = create; break; 50 | case state.read: receiver = read; break; 51 | case state.write: receiver = write; break; 52 | default: return {...state}; 53 | } 54 | return {...state, ...receiver(state)}; 55 | } 56 | 57 | /** 58 | * Reads the memory location pointed by the address register, and 59 | * produces its contents on the data register. 60 | * 61 | * @param {RAM} state 62 | * @returns {RAM} 63 | */ 64 | function read(state) { 65 | const ar = state.ar; 66 | const dr = state.data[ar]; 67 | return {ar, dr}; 68 | } 69 | 70 | /** 71 | * Writes the contents of the data register to the memory location 72 | * pointed by the address register. 73 | * 74 | * @param {RAM} state 75 | * @returns {RAM} 76 | */ 77 | function write(state) { 78 | const {ar, dr} = state; 79 | const data = [...state.data]; 80 | data[ar] = dr; 81 | return {data}; 82 | } 83 | 84 | /** 85 | * Returns an overview of the state of a RAM as text. 86 | * 87 | * @param {RAM} state 88 | * @returns {String} 89 | */ 90 | export function toString(state) { 91 | const rst = state.rst? 'on' : 'off'; 92 | const read = state.read? 'on' : 'off'; 93 | const write = state.write? 'on' : 'off'; 94 | const {ar, dr} = state; 95 | return `RAM RST:${rst} READ:${read} WRITE:${write} AR:${ar} DR:${dr}`; 96 | } 97 | -------------------------------------------------------------------------------- /src/System/System.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions to create and operate on complete systems. 3 | * 4 | * @module System/System 5 | */ 6 | 7 | import * as CPU from './CPU'; 8 | import * as RAM from './RAM'; 9 | import * as Printer from './Printer'; 10 | 11 | 12 | /** 13 | * A system in a virgin state. 14 | * 15 | * @typedef {Object} System 16 | * @property {CPU} cpu - CPU device 17 | * @property {RAM} ram - RAM device 18 | * @property {Printer} printer - Printer device 19 | * @property {Number} cycle - Current cycle number 20 | */ 21 | 22 | 23 | /** 24 | * A generic device. 25 | * 26 | * @typedef {Object} Device 27 | */ 28 | 29 | 30 | /** 31 | * Specification for a system bus. 32 | * 33 | * This bus specifies the order of creation and execution of devices in the 34 | * system, and which lines will be synchronized between devices after each 35 | * device has cycled. 36 | * 37 | * @typedef {Array} Bus 38 | * @property {String} Bus[].id - ID of device in the bus 39 | * @property {Module} Bus[].device - Module of device to instantiate 40 | * @property {Array} Bus[].lines - Synchronization lines to other devices 41 | */ 42 | export const Bus = [{ 43 | id: 'cpu', 44 | device: CPU, 45 | lines: { 46 | ram: ['read', 'write', 'ar', 'dr'], 47 | printer: ['output', 'or'], 48 | }, 49 | }, { 50 | id: 'ram', 51 | device: RAM, 52 | lines: { 53 | cpu: ['dr'], 54 | }, 55 | }, { 56 | id: 'printer', 57 | device: Printer, 58 | lines: {}, 59 | }]; 60 | 61 | 62 | /** 63 | * Create a system in a virgin state. 64 | * 65 | * @method create 66 | * @returns {System} 67 | */ 68 | export function create() { 69 | const system = { 70 | cycle: 0, 71 | }; 72 | for (const {id, device} of Bus) { 73 | system[id] = device.create(); 74 | } 75 | return system; 76 | } 77 | 78 | /** 79 | * Executes one system cycle by cycling devices, and returning the 80 | * new state. 81 | * 82 | * @param {System} state 83 | * @returns {System} 84 | */ 85 | export function cycle(state) { 86 | state = { 87 | ...state, 88 | cycle: state.cycle + 1, 89 | }; 90 | for (const {id, device, lines} of Bus) { 91 | state[id] = device.cycle(state[id]); 92 | for (const [target, mapping] of Object.entries(lines)) { 93 | for (const line of mapping) { 94 | state[target][line] = state[id][line]; 95 | } 96 | } 97 | } 98 | return state; 99 | } 100 | 101 | /** 102 | * Returns an overview of the state of a System as text. 103 | * 104 | * @param {System} state 105 | * @returns {String} 106 | */ 107 | export function toString(state) { 108 | return `${state.cycle}${Bus.map( 109 | ({id, device}) => `\t\t${device.toString(state[id])}\n` 110 | )}`; 111 | } 112 | -------------------------------------------------------------------------------- /src/System/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Components and utilities to create, operate and analyze 3 | * a Chip3 computer. 4 | * 5 | * @module System 6 | */ 7 | 8 | export * as CPU from './CPU'; 9 | export * as RAM from './RAM'; 10 | export * as System from './System'; 11 | -------------------------------------------------------------------------------- /src/Workbench/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {PropTypes as T} from 'react'; 4 | import {AppContainer} from 'react-hot-loader'; 5 | import Component from './Component'; 6 | import Timer from './Timer'; 7 | import Toolbar from './Toolbar'; 8 | import Layout from './Layout'; 9 | import HistoryPane from './HistoryPane'; 10 | import RAMPane from './RAMPane'; 11 | import CPUPane from './CPUPane'; 12 | import PrinterPane from './PrinterPane'; 13 | import {System} from 'System'; 14 | import {clamp} from './utils'; 15 | 16 | 17 | export default class App extends Component { 18 | 19 | static propTypes = { 20 | historySize: T.number, 21 | program: T.array, 22 | }; 23 | 24 | static defaultProps = { 25 | historySize: 500, 26 | program: [], 27 | }; 28 | 29 | state = { 30 | currentSnapshot: 0, 31 | history: [], 32 | running: false, 33 | interval: 0, 34 | }; 35 | 36 | lastCycleDuration = 0; 37 | lastCycleTime = 0; 38 | 39 | componentWillMount() { 40 | this.create(); 41 | } 42 | 43 | create = () => { 44 | const system = System.create(); 45 | const {data} = system.ram; 46 | const {program} = this.props; 47 | // Load program into memory 48 | system.ram.data = data.map((word, i) => program[i] || word); 49 | this.replaceHistory([system]); 50 | } 51 | 52 | cycle = () => { 53 | const current = this.getCurrentSnapshot(); 54 | const next = System.cycle(current); 55 | this.pushSnapshot(next); 56 | // Measure execution time 57 | const now = Date.now(); 58 | this.lastCycleDuration = now - this.lastCycleTime; 59 | this.lastCycleTime = now; 60 | // Pause if the machine is halted 61 | if (next.cpu.phase === 'HALT') this.pause(); 62 | } 63 | 64 | run = () => { 65 | this.setState({running: true}); 66 | } 67 | 68 | pause = () => { 69 | this.setState({running: false}); 70 | } 71 | 72 | step = () => { 73 | this.setState( 74 | {running: false}, 75 | () => this.cycle() 76 | ); 77 | } 78 | 79 | reset = () => { 80 | this.setState( 81 | {running: false}, 82 | () => this.create() 83 | ); 84 | } 85 | 86 | 87 | getCurrentSnapshot() { 88 | const current = this.state.currentSnapshot; 89 | const history = this.state.history; 90 | return history[current]; 91 | } 92 | 93 | selectSnapshot = (index) => { 94 | const length = this.state.history.length; 95 | const current = clamp(0, index, length-1); 96 | this.setState({currentSnapshot:current}); 97 | } 98 | 99 | pushSnapshot(state) { 100 | const current = this.state.currentSnapshot; 101 | const history = this.state.history 102 | .slice(0, current+1) 103 | .concat(state); 104 | this.replaceHistory(history); 105 | } 106 | 107 | replaceHistory(history) { 108 | const historySize = clamp(1, this.props.historySize); 109 | history = history.slice(-historySize); 110 | this.setState({history, currentSnapshot:history.length-1}); 111 | } 112 | 113 | static style = { 114 | width: '100vw', 115 | height: '100vh', 116 | overflow: 'hidden', 117 | } 118 | 119 | render() { 120 | const {interval, running} = this.state; 121 | const history = this.state.history; 122 | const current = this.state.currentSnapshot; 123 | const system = this.getCurrentSnapshot(); 124 | 125 | return ( 126 | 127 | 128 | 134 | 135 | 138 | 140 | 141 | 142 | 143 | 144 | 145 | 148 | 149 | 150 | ); 151 | } 152 | 153 | static bootstrap(selector, props) { 154 | const $element = document.querySelector(selector); 155 | return ReactDOM.render(, $element); 156 | }; 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/Workbench/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Component from './Component'; 3 | import {PropTypes as T} from 'react'; 4 | 5 | 6 | export default class Button extends Component { 7 | 8 | static propTypes = { 9 | children: T.node, 10 | disabled: T.bool, 11 | onClick: T.func.isRequired, 12 | }; 13 | 14 | static defaultProps = { 15 | children: 'Button', 16 | disabled: false, 17 | }; 18 | 19 | static style = { 20 | padding: '.3rem .85rem', 21 | border: 'solid thin #ddd', 22 | borderBottomColor: '#bbb', 23 | borderRadius: '.3rem', 24 | background: 'white', 25 | }; 26 | 27 | state = { 28 | hover: false, 29 | active: false, 30 | }; 31 | 32 | computeStyle() { 33 | const {disabled} = this.props; 34 | const {hover, active} = this.state; 35 | const highlighted = active && hover; 36 | return { 37 | backgroundColor: highlighted? '#e0e0e0' : 'white', 38 | pointerEvents: disabled? 'none' : 'auto', 39 | opacity: disabled? 0.6 : 1, 40 | }; 41 | } 42 | 43 | render() { 44 | return ( 45 | 52 | ); 53 | } 54 | 55 | onMouseEnter = () => this.setState({hover:true}); 56 | onMouseLeave = () => this.setState({hover:false}); 57 | 58 | onMouseDown = () => { 59 | // This way we can receive `mouseup` outside the element. 60 | window.addEventListener('mouseup', this.onMouseUp); 61 | this.setState({active:true}); 62 | }; 63 | 64 | onMouseUp = () => { 65 | window.removeEventListener('mouseup', this.onMouseUp); 66 | this.setState({active:false}); 67 | }; 68 | 69 | componentWillUnmount() { 70 | this.onMouseUp(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/Workbench/CPUPane.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import Component from './Component'; 4 | import Layout from './Layout'; 5 | import {hexString, byteFormats} from './utils'; 6 | 7 | 8 | export default class CPUPane extends Component { 9 | 10 | static propTypes = { 11 | cpu: T.object, 12 | }; 13 | 14 | static style = { 15 | pane: { 16 | flex: '0 0 auto', 17 | flexFlow: 'row wrap', 18 | background: '#f6f6f6', 19 | borderLeft: 'solid thin #bbb', 20 | }, 21 | prop: { 22 | display: 'flex', 23 | flex: '1 0 50%', 24 | padding: '.7rem 1rem', 25 | borderBottom: 'solid thin #ddd', 26 | }, 27 | dim: { 28 | color: '#bbb', 29 | }, 30 | propLabel: { 31 | flexBasis: 50, 32 | color: '#bbb', 33 | marginRight: '1rem', 34 | textAlign: 'right', 35 | }, 36 | propValue: { 37 | fontFamily: 'lucida console, monospace', 38 | marginTop: '.08rem', 39 | marginRight: '1rem', 40 | }, 41 | }; 42 | 43 | render() { 44 | const cpu = this.props.cpu; 45 | return ( 46 | 47 | {this.renderProp({ 48 | label: 'Phase', 49 | hint: 'Current phase', 50 | value: cpu.phase})} 51 | {this.renderProp({ 52 | label: 'Next', 53 | hint: 'Next phase', 54 | value: cpu.next})} 55 | {this.renderProp({ 56 | label: 'RST', 57 | hint: 'Reset flag', 58 | value: cpu.rst})} 59 | {this.renderProp({ 60 | label: 'READ', 61 | hint: 'Memory read flag', 62 | value: cpu.read})} 63 | {this.renderProp({ 64 | label: 'WRITE', 65 | hint: 'Memory write flag', 66 | value: cpu.write})} 67 | {this.renderProp({ 68 | label: 'OUTPUT', 69 | hint: 'I/O output', 70 | value: cpu.output})} 71 | {this.renderProp({ 72 | label: 'A', 73 | hint: 'Accumulator', 74 | value: cpu.a})} 75 | {this.renderProp({ 76 | label: 'IR', 77 | hint: 'Instruction Register', 78 | value: cpu.ir})} 79 | {this.renderProp({ 80 | label: 'AR', 81 | hint: 'Address Register', 82 | value: cpu.ar})} 83 | {this.renderProp({ 84 | label: 'DR', 85 | hint: 'Data Register', 86 | value: cpu.dr})} 87 | {this.renderProp({ 88 | label: 'OR', 89 | hint: 'Output Register', 90 | value: cpu.or})} 91 | {this.renderProp({ 92 | label: 'LR', 93 | hint: 'Last load address Register', 94 | value: cpu.ar})} 95 | {this.renderProp({ 96 | label: 'PC', 97 | hint: 'Program Counter', 98 | value: cpu.pc})} 99 | 100 | ); 101 | } 102 | 103 | renderProp(prop) { 104 | const {style} = this; 105 | const {label, value, size} = prop; 106 | const hint = typeof value === 'number' 107 | ? `${prop.hint} - ${byteFormats(value)}` 108 | : prop.hint; 109 | return ( 110 | 112 |
113 | {label} 114 |
115 | {typeof value === 'string' && ( 116 |
117 | {value} 118 |
119 | )} 120 | {typeof value === 'boolean' && ( 121 |
122 | {value? 'ON' : 'OFF'} 123 |
124 | )} 125 | {typeof value === 'number' && ( 126 |
127 | {hexString(value)} 128 |
129 | )} 130 |
131 | ); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/Workbench/Component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import shallowCompare from 'react/lib/shallowCompare'; 4 | 5 | 6 | export default class Component extends React.Component { 7 | 8 | static propTypes = { 9 | style: T.object, 10 | }; 11 | 12 | get style() { 13 | if (this.cachedStyle) { 14 | return this.cachedStyle; 15 | } 16 | this.cachedStyle = { 17 | ...this.constructor.style, 18 | ...(this.computeStyle && this.computeStyle()), 19 | ...this.props.style, 20 | }; 21 | return this.cachedStyle; 22 | } 23 | 24 | componentWillUpdate() { 25 | if (this.computeStyle) { 26 | this.cachedStyle = null; 27 | } 28 | } 29 | 30 | shouldComponentUpdate(nextProps, nextState) { 31 | return shallowCompare(this, nextProps, nextState); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Workbench/HistoryPane.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import Component from './Component'; 4 | import Layout from './Layout'; 5 | 6 | 7 | export default class HistoryPane extends Component { 8 | 9 | static propTypes = { 10 | current: T.number, 11 | history: T.array, 12 | onSelect: T.func, 13 | }; 14 | 15 | static style = { 16 | pane: { 17 | background: '#f6f6f6', 18 | borderRight: 'solid thin #bbb', 19 | }, 20 | row: { 21 | padding: '.7rem 1rem', 22 | borderBottom: 'solid thin #ddd', 23 | }, 24 | rowActive: { 25 | fontWeight: 'bold', 26 | background: '#eee', 27 | }, 28 | }; 29 | 30 | render() { 31 | return ( 32 | 33 |
34 | {this.props.history.map(::this.renderRow).reverse()} 35 |
36 |
37 | ); 38 | } 39 | 40 | renderRow(snapshot, i) { 41 | const active = i === this.props.current; 42 | const style = {...this.style.row, ...(active&&this.style.rowActive)}; 43 | return ( 44 |
this.props.onSelect(i)}> 46 | Clock cycle {snapshot.cycle} - {snapshot.cpu.phase} 47 |
48 | ); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Workbench/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import Component from './Component'; 4 | 5 | 6 | export default class Layout extends Component { 7 | 8 | static propTypes = { 9 | align: T.oneOf(['start', 'end', 'center', 'stretch']), 10 | children: T.node, 11 | dir: T.oneOf(['vertical', 'horizontal']), 12 | flex: T.number, 13 | justify: T.oneOf(['start', 'center', 'end', 'between', 'around']), 14 | size: T.number, 15 | title: T.string, 16 | }; 17 | 18 | static defaultProps = { 19 | flex: 1, 20 | dir: 'vertical', 21 | align: 'stretch', 22 | justify: 'between', 23 | }; 24 | 25 | static style = { 26 | display: 'flex', 27 | overflowY: 'auto', 28 | }; 29 | 30 | static stylePropMap = { 31 | 'vertical': 'column', 32 | 'horizontal': 'row', 33 | 'start': 'flex-start', 34 | 'end': 'flex-end', 35 | 'center': 'center', 36 | 'between': 'space-between', 37 | 'around': 'space-around', 38 | 'stretch': 'stretch', 39 | }; 40 | 41 | computeStyle() { 42 | return { 43 | flexGrow: this.props.size? 0 : this.props.flex, 44 | flexShrink: this.props.size? 1 : this.props.flex, 45 | flexBasis: this.props.size? this.props.size : 'auto', 46 | flexDirection: Layout.stylePropMap[this.props.dir], 47 | alignItems: Layout.stylePropMap[this.props.align], 48 | justifyContent: Layout.stylePropMap[this.props.justify], 49 | }; 50 | } 51 | 52 | render() { 53 | return ( 54 |
56 | {this.props.children} 57 |
58 | ); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/Workbench/PrinterPane.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import Component from './Component'; 4 | import Layout from './Layout'; 5 | import {hexString, bitString} from './utils'; 6 | 7 | 8 | export default class PrinterPane extends Component { 9 | 10 | static propTypes = { 11 | printer: T.object, 12 | }; 13 | 14 | static style = { 15 | pane: { 16 | flex: '1 1 0', 17 | padding: '0 16px', 18 | background: '#f6f6f6', 19 | borderTop: 'solid thin #bbb', 20 | borderLeft: 'solid thin #bbb', 21 | boxShadow: 'inset 0 8px 5px -5px rgba(0, 0, 0, .1)', 22 | fontFamily: 'menlo, monospace', 23 | }, 24 | paper: { 25 | flex: '1 1 0', 26 | background: '#fff', 27 | borderLeft: 'solid thin #ddd', 28 | borderRight: 'solid thin #ddd', 29 | boxShadow: 'inset 0 8px 5px -5px rgba(0, 0, 0, .1)', 30 | }, 31 | line: { 32 | flex: '0 0 auto', 33 | padding: '12px 20px', 34 | borderBottom: 'solid thin #ddd', 35 | }, 36 | lineHex: { 37 | flex: '0 0 auto', 38 | marginRight: 8, 39 | textAlign: 'center', 40 | }, 41 | lineChar: { 42 | flex: '0 0 auto', 43 | marginRight: 8, 44 | textAlign: 'center', 45 | }, 46 | lineBinary: { 47 | flex: '1 1 0', 48 | textAlign: 'right', 49 | letterSpacing: 1, 50 | whiteSpace: 'pre', 51 | }, 52 | }; 53 | 54 | render() { 55 | const lines = this.props.printer.paper; 56 | 57 | return ( 58 | 59 | 60 | {lines.map(this.renderLine)} 61 | 62 | 63 | ); 64 | } 65 | 66 | renderLine = (line, i) => { 67 | return ( 68 | 69 |
70 | {hexString(line)} 71 |
72 |
73 | {String.fromCharCode(line)} 74 |
75 |
76 | {bitString(line, 8, ' ', '▓▓')} 77 |
78 |
79 | ); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/Workbench/RAMPane.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import Component from './Component'; 4 | import Layout from './Layout'; 5 | import {hexString, bitString, byteFormats} from './utils'; 6 | import * as CPU from 'System/CPU'; 7 | 8 | 9 | export default class RAMPane extends Component { 10 | 11 | static propTypes = { 12 | pc: T.number, 13 | ram: T.object, 14 | }; 15 | 16 | static style = { 17 | row: { 18 | color: '#bbb', 19 | padding: '0 1rem', 20 | borderBottom: 'solid thin #ddd', 21 | fontFamily: 'lucida console, monospace', 22 | }, 23 | currentRow: { 24 | color: 'inherit', 25 | }, 26 | arRow: { 27 | background: '#e6f5ff', 28 | }, 29 | pcRow: { 30 | background: '#ffefed', 31 | }, 32 | cell: { 33 | margin: '0 1rem', 34 | padding: '.93rem 0 .7rem', 35 | }, 36 | addr: {}, 37 | short: { 38 | flex: '0 0 35px', 39 | textAlign: 'center', 40 | }, 41 | long: { 42 | flex: '1 1 0', 43 | textAlign: 'left', 44 | }, 45 | reg: { 46 | flexBasis: 35, 47 | textAlign: 'right', 48 | }, 49 | read: { 50 | color: '#0099ff', 51 | }, 52 | write: { 53 | color: '#e94c3d', 54 | }, 55 | ar: { 56 | color: '#0099ff', 57 | }, 58 | pc: { 59 | color: '#e94c3d', 60 | }, 61 | }; 62 | 63 | render() { 64 | return ( 65 | 66 |
67 | {this.props.ram.data.map(::this.renderRow)} 68 |
69 |
70 | ); 71 | } 72 | 73 | renderRow(word, i) { 74 | const {style} = this; 75 | const {ram, pc} = this.props; 76 | const activity = i===ram.ar 77 | && ((ram.read && 'READ') || (ram.write && 'WRITE')); 78 | const rowStyle = { 79 | ...style.row, 80 | ...(i===ram.ar && style.arRow), 81 | ...(i===pc && style.pcRow), 82 | ...((i===ram.ar || i===pc) && style.currentRow), 83 | }; 84 | const activityStyle = { 85 | ...style.reg, 86 | ...(ram.read && style.read), 87 | ...(ram.write && style.write), 88 | }; 89 | return ( 90 | 91 | {this.renderCell({ 92 | style: style.addr, 93 | hint: `Address - ${byteFormats(i)}`, 94 | value: bitString(i, 5)})} 95 | {this.renderCell({ 96 | style: style.short, 97 | value: String.fromCharCode(word)})} 98 | {this.renderCell({ 99 | style: style.short, 100 | hint: `Address contents - ${byteFormats(i)}`, 101 | value: hexString(word)})} 102 | {this.renderCell({ 103 | style: style.long, 104 | value: bitString(word)})} 105 | {this.renderCell({ 106 | style: style.long, 107 | hint: 'Disassembly', 108 | value: this.disassembled(word)})} 109 | {this.renderCell({ 110 | style: activityStyle, 111 | hint: 'Read/write activity', 112 | value: activity})} 113 | {this.renderCell({ 114 | style: {...style.reg, ...style.ar}, 115 | hint: 'Address Register', 116 | value: ram.ar === i && ' 122 | ); 123 | } 124 | 125 | renderCell(cell) { 126 | const {hint, style, value} = cell; 127 | return ( 128 |
130 | {value} 131 |
132 | ); 133 | } 134 | 135 | disassembled(word) { 136 | const {opcode, ar} = CPU.decode(word); 137 | const {mnemonic, operands} = opcode; 138 | switch (operands[0]) { 139 | case 'address': 140 | return `${mnemonic} [${bitString(ar, 5)}]`; 141 | case 'operator': 142 | let operators = CPU.decodeOperator(ar); 143 | return operators.map(o => o.mnemonic).join(' '); 144 | default: 145 | return mnemonic; 146 | } 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/Workbench/Timer.jsx: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | import {PropTypes as T} from 'react'; 3 | 4 | 5 | export default class Timer extends Component { 6 | 7 | static propTypes = { 8 | interval: T.number.isRequired, 9 | onTick: T.func.isRequired, 10 | running: T.bool.isRequired, 11 | }; 12 | 13 | componentWillMount() { 14 | if (this.props.running) { 15 | this.tick(); 16 | } 17 | } 18 | 19 | componentWillUnmount() { 20 | this.stop(); 21 | } 22 | 23 | componentDidUpdate(prevProps) { 24 | if (this.props.running !== prevProps.running) { 25 | if (this.props.running) { 26 | this.tick(); 27 | } 28 | } 29 | } 30 | 31 | tick = () => { 32 | if (this.props.running && this.props.onTick) { 33 | this.props.onTick(); 34 | this.schedule(this.tick, this.props.interval); 35 | } 36 | }; 37 | 38 | schedule(fn, timeout) { 39 | this.stop(); 40 | this.timer = setTimeout(fn, timeout); 41 | } 42 | 43 | stop() { 44 | clearTimeout(this.timer); 45 | this.timer = null; 46 | } 47 | 48 | render() { 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Workbench/Toolbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PropTypes as T} from 'react'; 3 | import Component from './Component'; 4 | import Layout from './Layout'; 5 | import Button from './Button'; 6 | 7 | 8 | export default class Toolbar extends Component { 9 | 10 | static propTypes = { 11 | onCycle: T.func.isRequired, 12 | onPause: T.func.isRequired, 13 | onReset: T.func.isRequired, 14 | onRun: T.func.isRequired, 15 | running: T.bool.isRequired, 16 | speed: T.number, 17 | }; 18 | 19 | static style = { 20 | flexShrink: 0, 21 | padding: '.4rem .6rem', 22 | background: '#eee', 23 | borderBottom: 'solid thin #bbb', 24 | }; 25 | 26 | render() { 27 | const { 28 | running, 29 | speed, 30 | onRun, 31 | onPause, 32 | onCycle, 33 | onReset, 34 | } = this.props; 35 | 36 | return ( 37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | {' '} 45 | {running && Speed: {Math.floor(speed)} Hz} 46 |
47 |
48 |
49 | ); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/Workbench/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chip3 Workbench 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 |
Loading...
20 | 21 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/Workbench/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A graphical inspector for the Chip3 system. 3 | * 4 | * @module Workbench 5 | */ 6 | 7 | export App from './App'; 8 | export Button from './Button'; 9 | export Component from './Component'; 10 | export Html from './index.html'; 11 | export Layout from './Layout'; 12 | export Toolbar from './Toolbar'; 13 | -------------------------------------------------------------------------------- /src/Workbench/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Workbench-related utilities. 3 | * 4 | * @module Workbench/utils 5 | */ 6 | 7 | 8 | /** 9 | * Clamps a value inside the given bounds, inclusive. 10 | * 11 | * @param {Number} [min=0] 12 | * @param {Number} x 13 | * @param {Number} [max=Infinity] 14 | * @returns {Number} 15 | */ 16 | export function clamp(min=0, x, max=Infinity) { 17 | return Math.max(min, Math.min(x, max)); 18 | } 19 | 20 | /** 21 | * Pads a string to a certain length by inserting filler characters on 22 | * its left, until it matches the requested length. 23 | * 24 | * Spaces are filled the specified character. If the requested length is 25 | * equal or less than the length of the string, the original string will 26 | * be returned as is. Numbers will be cast to string. 27 | * 28 | * @param {String|Number} x 29 | * @param {Number} len 30 | * @param {String} [fill='0'] 31 | * @returns {String} 32 | */ 33 | export function padString(x, len, fill='0') { 34 | while (x.length < len) x = fill + x; 35 | return x; 36 | } 37 | 38 | /** 39 | * Returns a hex string representation of a number. 40 | * 41 | * @param {Number} x 42 | * @param {Number} [pad=2] 43 | * @returns {String} 44 | */ 45 | export function hexString(x, pad=2) { 46 | const str = x.toString(16).toUpperCase(); 47 | return `0x${padString(str, pad)}`; 48 | } 49 | 50 | /** 51 | * Converts a byte to an array of bits. 52 | * 53 | * Optionally, specify printable characters to represent the states. 54 | * 55 | * @method bitArray 56 | * @param {Number} x 57 | * @param {Number} [len=8] 58 | * @param {Any} [off=0] 59 | * @param {Any} [on=1] 60 | * @returns {Array} 61 | */ 62 | export function bitArray(x, len=8, off=0, on=1) { 63 | const digits = [128, 64, 32, 16, 8, 4, 2, 1].slice(len * -1); 64 | /* eslint-disable no-bitwise */ 65 | return digits.map(d => (x & d) > 0? on : off); 66 | /* eslint-enable no-bitwise */ 67 | } 68 | 69 | /** 70 | * Converts a byte to string of bits. 71 | * 72 | * Optionally, specify printable characters to represent the states. 73 | * 74 | * @param {Number} x 75 | * @param {Number} [len] 76 | * @param {Any} [off='0'] 77 | * @param {Any} [on='1'] 78 | * @returns {String} 79 | */ 80 | export function bitString(x, len, off='0', on='1') { 81 | return bitArray(x, len, off, on).join(''); 82 | } 83 | 84 | /** 85 | * Returns a string with the value displayed in decimal, hexadecimal and 86 | * binary formats. 87 | * 88 | * @param {Number} x 89 | * @returns {String} 90 | */ 91 | export function byteFormats(x) { 92 | return [ 93 | `DEC=${x}`, 94 | `HEX=${hexString(x)}`, 95 | `BIN=${bitString(x)}`, 96 | `CHAR=${String.fromCharCode(x)}`, 97 | ].join(' '); 98 | } 99 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Chip3 is an interactive emulator of a simple computer emulator for 3 | * educational purposes. 4 | * 5 | * @module Chip3 6 | */ 7 | 8 | export * as System from './System'; 9 | export * as Assembler from './Assembler'; 10 | export * as Workbench from './Workbench'; 11 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------