gopp/tengo.go

175 lines
3.5 KiB
Go
Raw Permalink Normal View History

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() []byte
postCode func() []byte
preCompile func(*Script)
}
// Returns the new Tengo preprocessor
// with
func NewTengo() *Tengo {
ret := &Tengo{}
return ret
}
func (tengo *Tengo) SetPreCode(fn func() []byte) *Tengo {
tengo.preCode = fn
return tengo
}
func (tengo *Tengo) SetPostCode(fn func() []byte) *Tengo {
tengo.postCode = fn
return tengo
}
func (tengo *Tengo) SetPreCompile(fn func(*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,
codes [][]byte,
) ([][]byte, error) {
var fullCodeBuf bytes.Buffer
const retHead = `
__ret_one__ := bytes("")
pp := {
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 pp.preCode != nil {
fullCodeBuf.Write(pp.preCode())
}
for _, code := range codes {
fmt.Fprintln(&fullCodeBuf, "\n" + string(code) + retSeparator)
}
if pp.postCode != nil {
fullCodeBuf.Write(pp.postCode())
}
script := tengo.NewScript(fullCodeBuf.Bytes())
if pp.preCompile != nil {
pp.preCompile(script)
}
err := 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))
},
},
)
if err != nil {
return nil, err
}
err = 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))
},
},
)
if err != nil {
return nil, err
}
err = 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))
},
},
)
if err != nil {
return nil, err
}
err = 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
},
},
)
if err != nil {
return nil, err
}
_, err = script.RunContext(ctx)
if err != nil {
return nil, err
}
return rets, nil
}