Repository: goplus/gop
Pull request #2195 (2025-03-24 19:06:54)

cl: tplLit


Patch:
diff --git a/cl/expr.go b/cl/expr.go
index f82ac816..b99bc821 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -32,6 +32,7 @@ import (
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/printer"
 	"github.com/goplus/gop/token"
+	tpl "github.com/goplus/gop/tpl/ast"
 )
 
 /*-----------------------------------------------------------------------------
@@ -873,14 +874,14 @@ retry:
 
 func compileLambda(ctx *blockCtx, lambda ast.Expr, sig *types.Signature) {
 	switch expr := lambda.(type) {
-	case *ast.LambdaExpr:
-		if err := compileLambdaExpr(ctx, expr, sig); err != nil {
-			panic(err)
-		}
 	case *ast.LambdaExpr2:
 		if err := compileLambdaExpr2(ctx, expr, sig); err != nil {
 			panic(err)
 		}
+	case *ast.LambdaExpr:
+		if err := compileLambdaExpr(ctx, expr, sig); err != nil {
+			panic(err)
+		}
 	}
 }
 
@@ -1083,9 +1084,11 @@ func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) {
 	var cb = ctx.cb
 	var imp gogen.PkgRef
 	var name = v.Domain.Name
+	var path string
 	if pi, ok := ctx.findImport(name); ok {
 		imp = pi.PkgRef
-		if pi.Path() == "golang.org/x/net/html" {
+		path = pi.Path()
+		if path == "golang.org/x/net/html" {
 			// html`...` => html.Parse(strings.NewReader(`...`))
 			cb.Val(imp.Ref("Parse")).
 				Val(ctx.pkg.Import("strings").Ref("NewReader")).
@@ -1095,7 +1098,6 @@ func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) {
 			return
 		}
 	} else {
-		var path string
 		if name == "tpl" {
 			path = tplPkgPath
 		} else {
@@ -1108,9 +1110,54 @@ func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) {
 		}
 		*/
 	}
+
 	cb.Val(imp.Ref("New")).
-		Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v).
-		CallWith(1, 0, v)
+		Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v)
+
+	n := 1
+	if path == tplPkgPath {
+		if f, ok := v.Extra.(*tpl.File); ok {
+			decls := f.Decls
+			for _, decl := range decls {
+				if r, ok := decl.(*tpl.Rule); ok {
+					if expr, ok := r.RetProc.(*ast.LambdaExpr2); ok {
+						cb.Val(r.Name.Name)
+						sig := sigRetFunc(ctx.pkg, r.IsList())
+						compileLambdaExpr2(ctx, lambdaRetFunc(expr), sig)
+						n += 2
+					}
+				}
+			}
+		}
+	}
+	cb.CallWith(n, 0, v)
+}
+
+func lambdaRetFunc(expr *ast.LambdaExpr2) *ast.LambdaExpr2 {
+	v := *expr
+	v.Lhs = []*ast.Ident{
+		{NamePos: expr.Pos(), Name: "this"},
+	}
+	return &v
+}
+
+func sigRetFunc(pkg *gogen.Package, isList bool) *types.Signature {
+	rets := types.NewTuple(anyParam(pkg))
+	var args *types.Tuple
+	if isList {
+		args = types.NewTuple(anySliceParam(pkg))
+	} else {
+		args = rets
+	}
+	return types.NewSignatureType(nil, nil, nil, args, rets, false)
+}
+
+func anyParam(pkg *gogen.Package) *types.Var {
+	return pkg.NewParam(token.NoPos, "", gogen.TyEmptyInterface)
+}
+
+func anySliceParam(pkg *gogen.Package) *types.Var {
+	return pkg.NewParam(token.NoPos, "", types.NewSlice(gogen.TyEmptyInterface))
 }
 
 const (
diff --git a/tpl/ast/ast.go b/tpl/ast/ast.go
index f4eecf43..25f87c58 100644
--- a/tpl/ast/ast.go
+++ b/tpl/ast/ast.go
@@ -75,6 +75,25 @@ type Rule struct {
 	RetProc Node // => { ... } (see gop/ast.LambdaExpr2) or nil
 }
 
+// IsList reports whether the rule is a list rule.
+func (p *Rule) IsList() bool {
+	switch e := p.Expr.(type) {
+	case *Sequence:
+		return true
+	case *UnaryExpr:
+		switch e.Op {
+		case token.MUL, token.ADD: // *R, +R
+			return true
+		}
+	case *BinaryExpr:
+		switch e.Op {
+		case token.REM: // R1 % R2
+			return true
+		}
+	}
+	return false
+}
+
 func (p *Rule) Pos() token.Pos { return p.Name.Pos() }
 func (p *Rule) End() token.Pos {
 	if p.RetProc != nil {


Test patch:
diff --git a/cl/_testgop/domaintpl/in.gop b/cl/_testgop/domaintpl/in.gop
new file mode 100644
index 00000000..fb1c8a05
--- /dev/null
+++ b/cl/_testgop/domaintpl/in.gop
@@ -0,0 +1,49 @@
+tpl`
+file = stmts => {
+	return this
+}
+
+stmts = *(stmt ";") => {
+	return [n.([]any)[0] for n in this]
+}
+
+stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt
+
+varStmt = "DECLARE" namelist ":" typeExpr
+
+constStmt = "CONSTANT" IDENT "<-" expr
+
+assignStmt = IDENT "<-" expr
+
+outputStmt = "OUTPUT" exprlist
+
+inputStmt = "INPUT" namelist
+
+ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF"
+
+whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE"
+
+untilStmt = "REPEAT" ";" stmts "UNTIL" expr
+
+typeExpr = "INTEGER" | "REAL" | "STRING" | "BOOLEAN"
+
+expr = binaryExpr2 % ("<" | "<=" | ">" | ">=" | "=" | "<>")
+
+binaryExpr2 = binaryExpr1 % ("+" | "-")
+
+binaryExpr1 = operand % ("*" | "/")
+
+operand = basicLit | ident | parenExpr | unaryExpr
+
+unaryExpr = "-" operand
+
+basicLit = INT | FLOAT | STRING
+
+ident = IDENT
+
+parenExpr = "(" expr ")"
+
+exprlist = expr % ","
+
+namelist = IDENT % ","
+`
diff --git a/cl/_testgop/domaintpl/out.go b/cl/_testgop/domaintpl/out.go
new file mode 100644
index 00000000..4871fe43
--- /dev/null
+++ b/cl/_testgop/domaintpl/out.go
@@ -0,0 +1,64 @@
+package main
+
+import "github.com/goplus/gop/tpl"
+
+func main() {
+	tpl.New(`
+file = stmts => {
+	return this
+}
+
+stmts = *(stmt ";") => {
+	return [n.([]any)[0] for n in this]
+}
+
+stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt
+
+varStmt = "DECLARE" namelist ":" typeExpr
+
+constStmt = "CONSTANT" IDENT "<-" expr
+
+assignStmt = IDENT "<-" expr
+
+outputStmt = "OUTPUT" exprlist
+
+inputStmt = "INPUT" namelist
+
+ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF"
+
+whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE"
+
+untilStmt = "REPEAT" ";" stmts "UNTIL" expr
+
+typeExpr = "INTEGER" | "REAL" | "STRING" | "BOOLEAN"
+
+expr = binaryExpr2 % ("<" | "<=" | ">" | ">=" | "=" | "<>")
+
+binaryExpr2 = binaryExpr1 % ("+" | "-")
+
+binaryExpr1 = operand % ("*" | "/")
+
+operand = basicLit | ident | parenExpr | unaryExpr
+
+unaryExpr = "-" operand
+
+basicLit = INT | FLOAT | STRING
+
+ident = IDENT
+
+parenExpr = "(" expr ")"
+
+exprlist = expr % ","
+
+namelist = IDENT % ","
+`, "file", func(this interface{}) interface{} {
+		return this
+	}, "stmts", func(this []interface{}) interface{} {
+		return func() (_gop_ret []interface{}) {
+			for _, n := range this {
+				_gop_ret = append(_gop_ret, n.([]interface{})[0])
+			}
+			return
+		}()
+	})
+}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 10cb6b76..f9099173 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -37,6 +37,12 @@ func codeErrorTestAst(t *testing.T, pkgname, filename, msg, src string) {
 	cltest.ErrorAst(t, pkgname, filename, msg, src)
 }
 
+func TestErrTplLit(t *testing.T) {
+	codeErrorTest(t, `bar.gop:1:18: not enough arguments to return
+	have ()
+	want (interface{})`, "tpl`a = INT => { return }`")
+}
+
 func TestErrSendStmt(t *testing.T) {
 	codeErrorTest(t, `bar.gop:3:8: can't send multiple values to a channel`, `
 	var a chan int