gopp/xgo.go

230 lines
4.5 KiB
Go

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)
}