gopp/tengo.go

175 lines
3.7 KiB
Go

package tpp
import (
"github.com/d5/tengo/v2"
"fmt"
"context"
"bytes"
)
type Script = tengo.Script
type Compiled = tengo.Compiled
type Tengo struct {
head string
compiledScripts map[string] *Compiled
// Functions to modify
// preprocessor for
// more specific purposes.
preCode func(context.Context) []byte
postCode func(context.Context) []byte
preCompile func(context.Context, *Script)
}
// Returns the new Tengo preprocessor
// with
func NewTengo() *Tengo {
ret := &Tengo{}
return ret
}
func (tengo *Tengo) SetPreCode(fn func(context.Context) []byte) *Tengo {
tengo.preCode = fn
return tengo
}
func (tengo *Tengo) SetPostCode(fn func(context.Context) []byte) *Tengo {
tengo.postCode = fn
return tengo
}
func (tengo *Tengo) SetPreCompile(fn func(context.Context, *Script)) *Tengo {
tengo.preCompile = fn
return tengo
}
// Simple Evaler implementation for the Tengo language
func (pp *Tengo) Eval(
ctx context.Context,
recompile bool,
filePath string,
preCode []byte,
codes [][]byte,
) ([][]byte, error) {
var fullCodeBuf bytes.Buffer
const retHead = `
context := {}
__ret_one__ := bytes("")
pp := immutable({
filepath: __filepath__,
printf : func(format, ...vals) {
__ret_one__ += __sprintf__(format, vals...)
},
print : func(...vals) {
__ret_one__ += __sprint__(vals...)
},
println : func(...vals) {
__ret_one__ += __sprintln__(vals...)
},
write_raw : func(bts) {
__ret_one__ += bytes(bts)
}
})
`
const retSeparator = `
__separate__(__ret_one__)
__ret_one__ = ""
`
rets := [][]byte{}
fmt.Fprint(&fullCodeBuf, retHead)
if preCode != nil {
fmt.Fprintln(
&fullCodeBuf,
string(preCode)+"\n",
)
}
if pp.preCode != nil {
fullCodeBuf.Write(pp.preCode(ctx))
fullCodeBuf.Write([]byte(retSeparator))
}
for _, code := range codes {
fmt.Fprintln(
&fullCodeBuf,
"\n" + string(code) + retSeparator,
)
}
if pp.postCode != nil {
fullCodeBuf.Write(pp.postCode(ctx))
fullCodeBuf.Write([]byte(retSeparator))
}
script := tengo.NewScript(fullCodeBuf.Bytes())
if pp.preCompile != nil {
pp.preCompile(ctx, script)
}
script.Add("__filepath__", filePath)
script.Add("__sprintf__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments
}
format, ok := tengo.ToString(args[0])
if !ok {
return nil, tengo.ErrInvalidArgumentType{
Expected: "string",
}
}
gargs := make([]any, len(args) - 1)
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i+1])
//fmt.Printf("shit: %q\n", gargs[i])
}
str := fmt.Sprintf(format, gargs...)
return tengo.FromInterface([]byte(str))
},
})
script.Add("__sprint__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
gargs := make([]any, len(args))
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i])
}
str := fmt.Sprint(gargs...)
return tengo.FromInterface([]byte(str))
},
})
script.Add("__sprintln__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
gargs := make([]any, len(args))
for i := range gargs {
gargs[i] = tengo.ToInterface(args[i])
}
str := fmt.Sprintln(gargs...)
return tengo.FromInterface([]byte(str))
},
})
script.Add("__separate__", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error){
if len(args) < 1 {
return nil, tengo.ErrWrongNumArguments
}
bts, ok := tengo.ToByteSlice(args[0])
if !ok {
return nil, tengo.ErrInvalidArgumentType{
}
}
rets = append(rets, bts)
return nil, nil
},
})
_, err := script.RunContext(ctx)
if err != nil {
return nil, err
}
return rets, nil
}