package main type Memory [65535]byte func (m *Memory) Read(address uint16) byte { return m[address] } func (m *Memory) ReadLong(address uint16) uint16 { return To16(m[address], m[address+1]) } func (m *Memory) Read2(address uint16) (byte, byte) { return m[address], m[address+1] // May error if called at max index for the low } func (m *Memory) Write(address uint16, value byte) { m[address] = value } func (m *Memory) WriteLong(address uint16, value uint16) { h, l := To8s(value) m.Write(address, h) address++ if address >= MaxUint16-1 { address = 0 } m.Write(address, l) } func (m *Memory) Write2(address uint16, high, low byte) { m.Write(address, high) address++ if address >= MaxUint16-1 { address = 0 } m.Write(address, low) } type Stack struct { pointer byte data [255]byte } func (s Stack) Peek() byte { if s.pointer == 0 { return s.data[len(s.data)-1] } return s.data[s.pointer-1] } func (s Stack) Peek2() (byte, byte) { low := s.Peek() var high byte if s.pointer == 1 { high = s.data[len(s.data)-1] } else if s.pointer == 0 { high = s.data[len(s.data)-2] } else { high = s.data[s.pointer-2] } return high, low } func (s *Stack) Push(b byte) { s.data[s.pointer] = b if s.pointer == MaxByte-1 { s.pointer = 0 } else { s.pointer++ } } func (s *Stack) Push2(h, l byte) { s.Push(h) s.Push(l) } func (s *Stack) PushLong(n uint16) { h, l := To8s(n) s.Push2(h, l) } func (s *Stack) Pop() byte { if s.pointer == 0 { s.pointer = 254 } else { s.pointer-- } return s.data[s.pointer] } func (s *Stack) Pop2() (byte, byte) { low := s.Pop() return s.Pop(), low } func (s *Stack) PopLong() (uint16) { h, l := s.Pop2() return To16(h,l) } /* The idea of the heap pointer is that after all * data and ops are written to the memory space the * heap space will start to get used up. However, * when a subroutine is called with `cal`, the current * HP will be added to the return stack along with the * return address. When `ret` is called, it will change * the PC to the return address and HP to its previous * position. This allows for temporary variable usage * within the heap space and clear things up as needed. * The only issue is how to do something like dynamically * read in a string to a memory space. Since the length is * not known in advance, the space cannot be reserved in * advance. As a result, there is a case to be made for * not doing this at all, and essentially just fillling * up space without freeing it, especially since many of * the references in question will have their sizes known * prior to runtime. Doing all sized references in a "data" * section makes sense. That would leave the heap section * for large dynamic runtime things... */ type Vm struct { PC uint16 // Current Position Mem Memory Prog Memory Data Stack Return Stack HP uint16 }