bird-vm/types.go

132 lines
2.9 KiB
Go

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
}