package pp import ( "github.com/d5/tengo/v2" "github.com/d5/tengo/v2/stdlib" "context" "strings" "fmt" ) type Preprocessor struct { // The string will be inserted in the // beginning of processed files. head string tags [2]string modules *tengo.ModuleMap } // Evaluate the expression and return the value // it would print. func (pp *Preprocessor) Eval(codes []string) ([]string, error) { const retHead = ` __ret_one__ := "" printf := func(format, ...vals) { __ret_one__ += sprintf(format, vals...) } print := func(...vals) { __ret_one__ += sprint(vals...) } println := func(...vals) { __ret_one__ += sprintln(vals...) } ` const retSeparator = ` __Separate(__ret_one__) __ret_one__ = "" ` rets := []string{} fullCode := retHead + "\n" + pp.head for _, code := range codes { fullCode += "\n" + code + retSeparator + "\n" } script := tengo.NewScript([]byte(fullCode)) script.SetImports(pp.modules) 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(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(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(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 } str, ok := tengo.ToString(args[0]) if !ok { return nil, tengo.ErrInvalidArgumentType{ } } rets = append(rets, str) return nil, nil }, }, ) if err != nil { return nil, err } _, err = script.RunContext(context.Background()) if err != nil { return nil, err } return rets, nil } // Get the new preprocessor with default options. func NewPp() *Preprocessor { pp := &Preprocessor{ tags: [2]string{ "", }, modules: stdlib.GetModuleMap(stdlib.AllModuleNames()...), } return pp } func (pp *Preprocessor) Process(data string) (string, error) { var b strings.Builder last := 0 texts := []string{} codes := []string{} for { idxStart := strings.Index(data[last:], pp.tags[0]) idxEnd := strings.Index(data[last:], pp.tags[1]) //fmt.Printf("cock %d %d %d\n", last, idxStart, idxEnd) if idxStart < 0 { if idxEnd >= 0 { return "", UnexpectedError{ What: "end tag", } } texts = append(texts, data[last:]) break } else if idxEnd < 0 { return "", UnexpectedError{ What: "start tag", } } text := data[last:idxStart] texts = append(texts, text) code := data[idxStart+len(pp.tags[0]):idxEnd] codes = append(codes, code) data = data[idxEnd + len(pp.tags[1]):] /*if len(data) > 0 && data[0] == '\n' { data = data[1:] }*/ } codeRets, err := pp.Eval(codes) if err != nil { return "", err } for i, codeRet := range codeRets { fmt.Fprint(&b, texts[i]) fmt.Fprintf(&b, codeRet) } fmt.Fprint(&b, texts[len(codeRets)]) return b.String(), nil }