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 }