feat: added interface to implement making possible to use other interpreter implementations and also for the standard Tengo implementation added way to import scripts.

This commit is contained in:
Andrey Parhomenko 2024-02-26 23:04:47 +05:00
parent 9f07bbb550
commit 1eb5e35fa3
7 changed files with 171 additions and 129 deletions

6
eval.go Normal file
View file

@ -0,0 +1,6 @@
package pp
type Evaler interface {
Tags() [2]string
Eval(string, []string) ([]string, error)
}

131
main.go
View file

@ -1,144 +1,25 @@
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
evaler Evaler
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 {
func NewPp(evaler Evaler) *Preprocessor {
pp := &Preprocessor{
tags: [2]string{
"<?",
"?>",
},
modules: stdlib.GetModuleMap(stdlib.AllModuleNames()...),
tags: evaler.Tags(),
}
pp.evaler = evaler
return pp
}
func (pp *Preprocessor) Process(data string) (string, error) {
func (pp *Preprocessor) Process(filePath string, data string) (string, error) {
var b strings.Builder
last := 0
texts := []string{}
@ -171,7 +52,7 @@ func (pp *Preprocessor) Process(data string) (string, error) {
data = data[1:]
}*/
}
codeRets, err := pp.Eval(codes)
codeRets, err := pp.evaler.Eval(filePath, codes)
if err != nil {
return "", err
}

137
tengo.go Normal file
View file

@ -0,0 +1,137 @@
package pp
import (
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"fmt"
"context"
)
type Tengo struct {
head string
modules *tengo.ModuleMap
}
func NewTengo() *Tengo {
evaler := &Tengo{}
evaler.modules = stdlib.GetModuleMap(stdlib.AllModuleNames()...)
return evaler
}
func (pp *Tengo) Tags() [2]string {
return [2]string{
"<?",
"?>",
}
}
// Simple Evaler implementation for the Tengo language
func (pp *Tengo) Eval(filePath string, 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)
script.EnableFileImport(true)
script.SetImportDir(".")
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
}

View file

@ -1,9 +1,20 @@
<?
newVar := "'this is gen shita'"
os := import("os")
shit := import("./tests/shit.tengo")
newVar := "'this is gen shita'"
println(shit.some_func())
?># The index testing
1 + 1 = <? print(1+1) ?>
cock <? print(newVar, "and cock")?>
## The shit after
checking <? printf(newVar)?>
checking <? printf(newVar) ?>
## File contents
<?
bts := os.read_file("tests/somefile")
printf("%v\n%s", bts, bts)
?>

6
tests/shit.tengo Normal file
View file

@ -0,0 +1,6 @@
some_func := func() {
return "THE RET VALUE OF SOME FUNC"
}
export({
some_func: some_func
})

1
tests/somefile Normal file
View file

@ -0,0 +1 @@
content of some file

View file

@ -9,7 +9,7 @@ import (
)
var Tool = mtool.T("pp").Func(func(flags *mtool.Flags){
pp := NewPp()
pp := NewPp(NewTengo())
filePaths := flags.Parse()
for _, filePath := range filePaths {
pth := filepath.FromSlash(filePath)
@ -18,7 +18,7 @@ var Tool = mtool.T("pp").Func(func(flags *mtool.Flags){
log.Println("read error:", err)
continue
}
str, err := pp.Process(string(bts))
str, err := pp.Process(pth, string(bts))
if err != nil {
log.Println("pp error:", err)
continue