// Package forge implements sort of a stack-based interpreter. package forge import ( "io" "os" "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 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 latestXT XT cstate bool output io.Writer } 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, output: os.Stdout, } } var newVoc = voc.NewVoc[fitem] var newPtr = ptr.NewSlice[fitem] // xitem (executable item) / XT (execution token) type xitem struct { name string immediate bool fct Callable body fptr } type XT = *xitem func Register(voc *fvoc, name string, fct Callable) *xitem { it := xitem{ name: name, fct: fct, } voc.Register(name, &it) return &it } func (it *xitem) IsImmediate() bool { return it.immediate } func (it *xitem) Immediate() { it.immediate = true } func (it *xitem) Body() fptr { if it.body == nil { it.body = newPtr() } return it.body } func (it *xitem) Name() string { return it.name } // forgeEnv methods func (f *forgeEnv) Voc() *fvoc { return f.voc } 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) Push(it fitem) { f.ds.Push(it) } func (f *forgeEnv) Pop() fitem { return f.ds.Pop() } func (f *forgeEnv) Literal() { f.Push(f.ip.Next().Value()) }