neoneo-go/_pkg.dev/vm/stack/context.go
Roman Khimov ddd1d92ff1 pkg: hide it by moving to _pkg.dev
The idea here is to preserve the history of `dev` branch development and its
code when merging with the `master`. Later this code could be moved into the
masters code where appropriate.
2019-08-20 18:39:50 +03:00

174 lines
3.7 KiB
Go

package stack
import (
"encoding/binary"
"errors"
"fmt"
)
// Context represent the current execution context of the VM.
// context will be treated as stack item and placed onto the invocation stack
type Context struct {
*abstractItem
// Instruction pointer.
ip int
// The raw program script.
prog []byte
// Breakpoints
breakPoints []int
// Evaluation Stack
Estack RandomAccess
// Alternative Stack
Astack RandomAccess
}
// NewContext return a new Context object.
func NewContext(b []byte) *Context {
return &Context{
abstractItem: &abstractItem{},
ip: -1,
prog: b,
breakPoints: []int{},
}
}
// Context overrides the default implementation
// to return a context item
func (c *Context) Context() (*Context, error) {
return c, nil
}
// Next return the next instruction to execute.
func (c *Context) Next() (Instruction, error) {
c.ip++
if c.ip >= len(c.prog) {
return RET, errors.New("program pointer is more than the length of program. Returning RET OPCODE")
}
return Instruction(c.prog[c.ip]), nil
}
// IP returns the absolute instruction without taking 0 into account.
// If that program starts the ip = 0 but IP() will return 1, cause its
// the first instruction.
func (c *Context) IP() int {
return c.ip + 1
}
// LenInstr returns the number of instructions loaded.
func (c *Context) LenInstr() int {
return len(c.prog)
}
// CurrInstr returns the current instruction and opcode.
func (c *Context) CurrInstr() (int, Instruction) {
if c.ip < 0 {
return c.ip, Instruction(0x00)
}
return c.ip, Instruction(c.prog[c.ip])
}
// Copy returns an new exact copy of c.
func (c *Context) Copy() *Context {
return &Context{
ip: c.ip,
prog: c.prog,
breakPoints: c.breakPoints,
}
}
// Program returns the loaded program.
func (c *Context) Program() []byte {
return c.prog
}
func (c *Context) atBreakPoint() bool {
for _, n := range c.breakPoints {
if n == c.ip {
return true
}
}
return false
}
func (c *Context) String() string {
return "execution context"
}
// ReadUint32 reads a uint32 from the script
func (c *Context) ReadUint32() uint32 {
start, end := c.IP(), c.IP()+4
if end > len(c.prog) {
return 0
}
val := binary.LittleEndian.Uint32(c.prog[start:end])
c.ip += 4
return val
}
// ReadUint16 reads a uint16 from the script
func (c *Context) ReadUint16() uint16 {
start, end := c.IP(), c.IP()+2
if end > len(c.prog) {
return 0
}
val := binary.LittleEndian.Uint16(c.prog[start:end])
c.ip += 2
return val
}
// ReadInt16 reads a int16 from the script
func (c *Context) ReadInt16() int16 {
return int16(c.ReadUint16())
}
// ReadByte reads one byte from the script
func (c *Context) ReadByte() (byte, error) {
byt, err := c.ReadBytes(1)
if err != nil {
return 0, err
}
return byt[0], nil
}
// ReadBytes will read n bytes from the context
func (c *Context) ReadBytes(n int) ([]byte, error) {
start, end := c.IP(), c.IP()+n
if end > len(c.prog) {
return nil, errors.New("Too many bytes to read, pointer goes past end of program")
}
out := make([]byte, n)
copy(out, c.prog[start:end])
c.ip += n
return out, nil
}
func (c *Context) readVarBytes() ([]byte, error) {
n, err := c.ReadByte()
if err != nil {
return nil, err
}
return c.ReadBytes(int(n))
}
// SetIP sets the instruction pointer ip to a given integer.
// Returns an error if ip is less than -1 or greater than LenInstr.
func (c *Context) SetIP(ip int) error {
if ok := ip < -1 || ip > c.LenInstr(); ok {
return errors.New("invalid instruction pointer")
}
c.ip = ip
return nil
}
// Hash overrides the default abstract hash method.
func (c *Context) Hash() (string, error) {
data := c.String() + fmt.Sprintf(" %v-%v-%v-%v-%v", c.ip, c.prog, c.breakPoints, c.Estack, c.Astack)
return KeyGenerator([]byte(data))
}