go-scopes/forge/forge.go

205 lines
3.2 KiB
Go

// Package forge implements sort of a stack-based interpreter.
package forge
import (
"git.sr.ht/~cco/go-scopes/common/ptr"
"git.sr.ht/~cco/go-scopes/common/stack"
"git.sr.ht/~cco/go-scopes/common/voc"
)
type fitem interface{}
type fptr = ptr.Ptr[fitem]
type FPtr = fptr
type fstack = stack.Stack[fitem]
type fvoc = voc.Vocabulary[fitem]
type Callable func(*forgeEnv, XT)
type forgeEnv struct {
ds, rs fstack
cp, ip, dp fptr
voc *fvoc
}
type FE = *forgeEnv
func NewFE() *forgeEnv {
return newFE(newVoc(nil))
}
func (f *forgeEnv) ChildFE() *forgeEnv {
return newFE(newVoc(f.voc))
}
func newFE(voc *fvoc) *forgeEnv {
return &forgeEnv{
ds: stack.NewStack[fitem](),
rs: stack.NewStack[fitem](),
ip: ptr.NewSlice[fitem](),
voc: voc,
}
}
var newVoc = voc.NewVoc[fitem]
var newScalar = ptr.NewScalar[fitem]
var newPtr = ptr.NewSlice[fitem]
// XT (execution token)
type XT interface {
Name() string
Fct() Callable
Code() fptr
Data() fptr
}
type name string
func (n name) Name() string {
return string(n)
}
// gofunc: XT consisting only of a function written in Go
type gofunc struct {
name
fct Callable
}
func GoFunc(n string, fct Callable) XT {
return &gofunc{name(n), fct}
}
func (it *gofunc) Fct() Callable {
return it.fct
}
func (it *gofunc) Code() fptr {
return nil
}
func (it *gofunc) Data() fptr {
return nil
}
// fcode: with forge code
type fcode struct {
name
code fptr
}
func FCode(n string, c fptr) XT {
return &fcode{name(n), c}
}
func (it *fcode) Fct() Callable {
return doDef
}
func (it *fcode) Code() fptr {
return it.code
}
func (it *fcode) Data() fptr {
return nil
}
// data: data provider, with optional forge code
type data struct {
name
code fptr
data fptr
}
func Data(n string, d, c fptr) XT {
return &data{name(n), c, d}
}
func (it *data) Fct() Callable {
return doData
}
func (it *data) Code() fptr {
return it.code
}
func (it *data) Data() fptr {
return it.data
}
// func Register(voc *fvoc, n string, fct Callable) *xitem {
func Register(voc *fvoc, it XT) XT {
voc.Register(it.Name(), it)
return it
}
// forgeEnv methods
func (f *forgeEnv) Code(items ...fitem) fptr {
code := newPtr(items...)
// ... pre-process (compile) code ...
return code
}
func (f *forgeEnv) Call(code fptr) {
f.rs.Push(f.ip)
f.ip = code.Clone()
for f.ip.Next() != nil {
xt := f.ip.Value().(XT)
xt.Fct()(f, xt)
}
f.ip = f.rs.Pop().(fptr)
}
func (f *forgeEnv) Exec(items ...fitem) {
f.Call(f.Code(items...))
}
func (f *forgeEnv) Def(name string, items ...fitem) XT {
code := f.Code(items...)
return Register(f.voc, FCode(name, code))
}
func (f *forgeEnv) Create(name string, value fitem, code fptr) XT {
data := newScalar().Set(value)
return Register(f.voc, Data(name, data, code))
}
func (f *forgeEnv) Literal() {
f.Push(f.ip.Next().Value())
}
func (f *forgeEnv) Voc() *fvoc {
return f.voc
}
func (f *forgeEnv) Push(it fitem) {
f.ds.Push(it)
}
func (f *forgeEnv) Pop() fitem {
return f.ds.Pop()
}
func (f *forgeEnv) Peek(d int) fitem {
return f.ds.Peek(d)
}
// basic functions for executable items
func doDef(f *forgeEnv, xt XT) {
f.Call(xt.Code().(fptr))
}
func doData(f *forgeEnv, xt XT) {
f.Push(xt.Data())
code := xt.Code()
if code != nil {
f.Call(code)
}
}