// 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) } }