About the LC-2
The LC-2 is a 16-bit CPU with 8 general-purpose registers (r0 to r7), 2 special purpose registers: PC, which stores the memory address of the next instruction to be loaded; and IR, which stores the most recently loaded instruction. Additionally, the LC-2’s memory management unit (MMU) has 2 additional registers: MAR, which stores an address in memory; and MDR, which stores the data at that address in memory. These registers are used to read and write to memory.
The simulated MMU in lc2.js uses typed arrays of unsigned 16-bit integers to model the memory. In order to minimize wasted memory, each page (9 bit index, 20 512 memory addresses) are only initialized when they are first read/written. For any reasonably program, lc2.js will only initialize 3 pages of memory. The first and last pages contain OS code, and user code exists in the page starting with address 0x3000 by convention. This means that lc2.js normally only uses 3kb of browser memory, rather than the full 131 kb (216 = 65536 memory locations, each of which is 2 bytes).
I wrote lc2.js in a test-driven way. I began by specifying a series of unit tests in QUnit, and then worked backwards from there to make the tests pass with minimal code changes. It worked well enough, but there was at least one instance where I wrote a wrong test, and only caught it when I was trying to run a real program.
The book that defines LC-2 has a gap. In one chapter, we are using LC-2 assembly, and in the next we are using C. I would like to use lc2.js to bridge that gap. I think it would be interesting to build a Forth on top of lc2.js, and use that to bootstrap a simple Lisp dialect.