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 }