package gopp import ( "surdeus.su/core/xgo/v2" "fmt" "io" "context" "bytes" "strconv" ) type CompiledMap map[string] *Compiled type Script = xgo.Script type Compiled = xgo.Compiled // The type describes // customizable way to evaluate // files. type XGo struct { // 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 XGo preprocessor // with func NewXGo() *XGo { ret := &XGo{} return ret } func (xgo *XGo) SetPreCode(fn func(context.Context) []byte) *XGo { xgo.preCode = fn return xgo } func (xgo *XGo) SetPostCode(fn func(context.Context) []byte) *XGo { xgo.postCode = fn return xgo } func (xgo *XGo) SetPreCompile(fn func(context.Context, *Script)) *XGo { xgo.preCompile = fn return xgo } const ( ppFilePath = "__pp_file_path__" ppPrintf = "__pp_printf__" ppPrint = "__pp_print__" ppPrintln = "__pp_println__" ppWriteRaw = "__pp_write_raw__" ppHead = `// The main generated head. Exists in every file. context := {} pp := immutable({ fpath:` + ppFilePath + `, printf :` + ppPrintf + `, print :` + ppPrint + `, println :` + ppPrintln + `, write_raw :` + ppWriteRaw + ` }) `) // Render to code. func (pp *XGo) Render( ctx context.Context, parsed *Parsed, output io.Writer, ) (error) { fmt.Fprint(output, ppHead) if parsed.PreCode != nil { output.Write([]byte("// The parsed precode from file.\n")) output.Write(parsed.PreCode) } if pp.preCode != nil { output.Write(pp.preCode(ctx)) } for i, code := range parsed.Codes { // Text. fmt.Fprintf( output, "// Text %d\n" + ppWriteRaw+"(%q)\n", i, parsed.Texts[i], ) // Code. fmt.Fprintf(output, "// Code %d\n", i) output.Write(code) } i := len(parsed.Texts)-1 fmt.Fprintf( output, "// Text %d\n" + ppWriteRaw+"(%q)\n", i, parsed.Texts[i], ) if pp.postCode != nil { output.Write(pp.postCode(ctx)) } return nil } // b func (pp *XGo) Compile( ctx context.Context, // Static text pieces. parsed *Parsed, ) (*Compiled, error) { var buf bytes.Buffer err := pp.Render( ctx, parsed, &buf, ) fullCode := buf.Bytes() if err != nil { return nil, err } script := xgo.NewScript(fullCode) if pp.preCompile != nil { pp.preCompile(ctx, script) } // Presetting variables before running. script.Add(ppFilePath, false) script.Add(ppPrintf, false) script.Add(ppPrint, false) script.Add(ppPrintln, false) script.Add(ppWriteRaw, false) compiled, err := script.Compile() if err != nil { return nil, err } return compiled, nil } func (pp *XGo) RunContext( ctx context.Context, compiled *Compiled, filePath string, output io.Writer, ) error { var err error err = compiled.Set(ppFilePath, filePath) if err != nil {return err} err = compiled.Set(ppPrintf, &xgo.UserFunction{ Value: func(args ...xgo.Object) (xgo.Object, error){ if len(args) < 1 { return nil, xgo.ErrWrongNumArguments } format, ok := xgo.ToString(args[0]) if !ok { return nil, xgo.ErrInvalidArgumentType{ Expected: "string", } } gargs := make([]any, len(args) - 1) for i := range gargs { gargs[i] = xgo.ToInterface(args[i+1]) //fmt.Printf("shit: %q\n", gargs[i]) } fmt.Fprintf(output, format, gargs...) return nil, nil //return xgo.FromInterface([]byte(str)) }, }) if err != nil {return err} err = compiled.Set(ppPrint, &xgo.UserFunction{ Value: func(args ...xgo.Object) (xgo.Object, error){ gargs := make([]any, len(args)) for i := range gargs { gargs[i] = xgo.ToInterface(args[i]) } fmt.Fprint(output, gargs...) return nil, nil //return xgo.FromInterface([]byte(str)) }, }) if err != nil {return err} err = compiled.Set(ppPrintln, &xgo.UserFunction{ Value: func(args ...xgo.Object) (xgo.Object, error){ gargs := make([]any, len(args)) for i := range gargs { gargs[i] = xgo.ToInterface(args[i]) } fmt.Fprintln(output, gargs...) return nil, nil //return xgo.FromInterface([]byte(str)) }, }) if err != nil {return err} err = compiled.Set(ppWriteRaw, &xgo.UserFunction{ Value: func(args ...xgo.Object) (xgo.Object, error){ bt := make([][]byte, len(args)) for i, o := range args { bts, ok := xgo.ToByteSlice(o) if !ok { return nil, xgo.ErrInvalidArgumentType{ Name: strconv.Itoa(i), Expected: "string/bytes", Found: o.TypeName(), } } bt[i] = bts } for _, b := range bt { output.Write(b) } return nil, nil }, }) if err != nil {return err} return compiled.RunContext(ctx) }