Repository: goplus/gop
Pull request #2193 (2025-03-24 16:53:48)

parser: tplLit


Patch:
diff --git a/parser/parser.go b/parser/parser.go
index 6fe8a31d..5e7ca8fc 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -36,6 +36,8 @@ import (
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
+	tplast "github.com/goplus/gop/tpl/ast"
+	tpl "github.com/goplus/gop/tpl/parser"
 	"github.com/qiniu/x/log"
 )
 
@@ -1723,6 +1725,24 @@ func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any {
 	return parts
 }
 
+func (p *parser) tplLit(off, end token.Pos) any {
+	file := p.file
+	base := file.Base()
+	src := p.scanner.CodeTo(int(end) - base)
+	expr, err := tpl.ParseEx(file, src, int(off)-base, &tpl.Config{
+		ParseRetProc: parseTplRetProc,
+	})
+	if err != nil {
+		p.errors = append(p.errors, err...)
+		return nil
+	}
+	return expr
+}
+
+func parseTplRetProc(file *token.File, src []byte, offset int) (tplast.Node, scanner.ErrorList) {
+	return ParseExprEx(file, src, offset, 0)
+}
+
 // parseOperand may return an expression or a raw type (incl. array
 // types of the form [...]T. Callers must verify the result.
 // If lhs is set and the result is an identifier, it is not resolved.
@@ -1736,13 +1756,19 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 		ident := p.parseIdent()
 		if p.tok == token.STRING && p.pos == ident.End() && strings.HasPrefix(p.lit, "`") {
 			// domain text: tpl`...`
+			var pos, lit = p.pos, p.lit
+			var extra any
+			if ident.Name == "tpl" {
+				extra = p.tplLit(pos+1, pos+token.Pos(len(lit))-1)
+			}
 			x = &ast.DomainTextLit{
 				Domain:   ident,
-				ValuePos: p.pos,
-				Value:    p.lit,
+				ValuePos: pos,
+				Value:    lit,
+				Extra:    extra,
 			}
 			if debugParseOutput {
-				log.Printf("ast.DomainTextLit{Domain: %s, Value: %s}\n", ident.Name, p.lit)
+				log.Printf("ast.DomainTextLit{Domain: %s, Value: %s}\n", ident.Name, lit)
 			}
 			p.next()
 		} else {
diff --git a/tpl/parser/parser.go b/tpl/parser/parser.go
index ff994c0e..e04beb3c 100644
--- a/tpl/parser/parser.go
+++ b/tpl/parser/parser.go
@@ -40,26 +40,26 @@ func ParseFile(fset *token.FileSet, filename string, src any, conf *Config) (f *
 		return nil, err
 	}
 	file := fset.AddFile(filename, -1, len(b))
-	return Parse(file, b, 0, conf)
+	f, errs := ParseEx(file, b, 0, conf)
+	switch errs.Len() {
+	case 0:
+	case 1:
+		err = errs[0]
+	default:
+		errs.Sort()
+		err = errs
+	}
+	return
 }
 
-// Parse parses src[offset:] and returns the AST.
-func Parse(file *token.File, src []byte, offset int, conf *Config) (f *ast.File, err error) {
+// ParseEx parses src[offset:] and returns the AST.
+func ParseEx(file *token.File, src []byte, offset int, conf *Config) (f *ast.File, errs scanner.ErrorList) {
 	var p parser
 	p.init(file, src, offset)
 	if conf != nil {
 		p.parseRetProc = conf.ParseRetProc
 	}
-	f = p.parseFile()
-	switch p.errors.Len() {
-	case 0:
-	case 1:
-		err = p.errors[0]
-	default:
-		p.errors.Sort()
-		err = p.errors
-	}
-	return
+	return p.parseFile(), p.errors
 }
 
 // -----------------------------------------------------------------------------


Test patch:
diff --git a/parser/_testdata/domaintext/parser.expect b/parser/_testdata/domaintext/parser.expect
index aadb4b7f..567affb2 100644
--- a/parser/_testdata/domaintext/parser.expect
+++ b/parser/_testdata/domaintext/parser.expect
@@ -34,4 +34,82 @@ unaryExpr = operand | "-" unaryExpr
 
 operand = INT | FLOAT | "(" expr ")"
 `
+                  Extra:
+                    ast.File:
+                      Decls:
+                        ast.Rule:
+                          Name:
+                            ast.Ident:
+                              Name: expr
+                          Expr:
+                            ast.BinaryExpr:
+                              X:
+                                ast.Ident:
+                                  Name: termExpr
+                              Op: %
+                              Y:
+                                ast.Choice:
+                                  Options:
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "+"
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "-"
+                        ast.Rule:
+                          Name:
+                            ast.Ident:
+                              Name: termExpr
+                          Expr:
+                            ast.BinaryExpr:
+                              X:
+                                ast.Ident:
+                                  Name: unaryExpr
+                              Op: %
+                              Y:
+                                ast.Choice:
+                                  Options:
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "*"
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "/"
+                        ast.Rule:
+                          Name:
+                            ast.Ident:
+                              Name: unaryExpr
+                          Expr:
+                            ast.Choice:
+                              Options:
+                                ast.Ident:
+                                  Name: operand
+                                ast.Sequence:
+                                  Items:
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "-"
+                                    ast.Ident:
+                                      Name: unaryExpr
+                        ast.Rule:
+                          Name:
+                            ast.Ident:
+                              Name: operand
+                          Expr:
+                            ast.Choice:
+                              Options:
+                                ast.Ident:
+                                  Name: INT
+                                ast.Ident:
+                                  Name: FLOAT
+                                ast.Sequence:
+                                  Items:
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "("
+                                    ast.Ident:
+                                      Name: expr
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: ")"
               Tok: !
diff --git a/parser/_testdata/domaintpl/parser.expect b/parser/_testdata/domaintpl/parser.expect
new file mode 100644
index 00000000..c9b793fe
--- /dev/null
+++ b/parser/_testdata/domaintpl/parser.expect
@@ -0,0 +1,146 @@
+package main
+
+file tpl.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: cl
+          Tok: :=
+          Rhs:
+            ast.ErrWrapExpr:
+              X:
+                ast.DomainTextLit:
+                  Domain:
+                    ast.Ident:
+                      Name: tpl
+                  Value: `
+file = stmts => {
+	return &ast.File{
+		Stmts: this.([]ast.Stmt),
+	}
+}
+
+stmts = *(stmt ";") => {
+	return [n.([]any)[0].(ast.Stmt) for n in this]
+}
+`
+                  Extra:
+                    ast.File:
+                      Decls:
+                        ast.Rule:
+                          Name:
+                            ast.Ident:
+                              Name: file
+                          Expr:
+                            ast.Ident:
+                              Name: stmts
+                          RetProc:
+                            ast.LambdaExpr2:
+                              Body:
+                                ast.BlockStmt:
+                                  List:
+                                    ast.ReturnStmt:
+                                      Results:
+                                        ast.UnaryExpr:
+                                          Op: &
+                                          X:
+                                            ast.CompositeLit:
+                                              Type:
+                                                ast.SelectorExpr:
+                                                  X:
+                                                    ast.Ident:
+                                                      Name: ast
+                                                  Sel:
+                                                    ast.Ident:
+                                                      Name: File
+                                              Elts:
+                                                ast.KeyValueExpr:
+                                                  Key:
+                                                    ast.Ident:
+                                                      Name: Stmts
+                                                  Value:
+                                                    ast.TypeAssertExpr:
+                                                      X:
+                                                        ast.Ident:
+                                                          Name: this
+                                                      Type:
+                                                        ast.ArrayType:
+                                                          Elt:
+                                                            ast.SelectorExpr:
+                                                              X:
+                                                                ast.Ident:
+                                                                  Name: ast
+                                                              Sel:
+                                                                ast.Ident:
+                                                                  Name: Stmt
+                        ast.Rule:
+                          Name:
+                            ast.Ident:
+                              Name: stmts
+                          Expr:
+                            ast.UnaryExpr:
+                              Op: *
+                              X:
+                                ast.Sequence:
+                                  Items:
+                                    ast.Ident:
+                                      Name: stmt
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: ";"
+                          RetProc:
+                            ast.LambdaExpr2:
+                              Body:
+                                ast.BlockStmt:
+                                  List:
+                                    ast.ReturnStmt:
+                                      Results:
+                                        ast.ComprehensionExpr:
+                                          Tok: [
+                                          Elt:
+                                            ast.TypeAssertExpr:
+                                              X:
+                                                ast.IndexExpr:
+                                                  X:
+                                                    ast.TypeAssertExpr:
+                                                      X:
+                                                        ast.Ident:
+                                                          Name: n
+                                                      Type:
+                                                        ast.ArrayType:
+                                                          Elt:
+                                                            ast.Ident:
+                                                              Name: any
+                                                  Index:
+                                                    ast.BasicLit:
+                                                      Kind: INT
+                                                      Value: 0
+                                              Type:
+                                                ast.SelectorExpr:
+                                                  X:
+                                                    ast.Ident:
+                                                      Name: ast
+                                                  Sel:
+                                                    ast.Ident:
+                                                      Name: Stmt
+                                          Fors:
+                                            ast.ForPhrase:
+                                              Value:
+                                                ast.Ident:
+                                                  Name: n
+                                              X:
+                                                ast.Ident:
+                                                  Name: this
+              Tok: !
diff --git a/parser/_testdata/domaintpl/tpl.gop b/parser/_testdata/domaintpl/tpl.gop
new file mode 100644
index 00000000..bc81b9e4
--- /dev/null
+++ b/parser/_testdata/domaintpl/tpl.gop
@@ -0,0 +1,11 @@
+cl := tpl`
+file = stmts => {
+	return &ast.File{
+		Stmts: this.([]ast.Stmt),
+	}
+}
+
+stmts = *(stmt ";") => {
+	return [n.([]any)[0].(ast.Stmt) for n in this]
+}
+`!
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 9172c33d..46e082b1 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -125,6 +125,10 @@ func TestErrLabel(t *testing.T) {
 	testErrCode(t, `a.x:`, `/foo/bar.gop:1:4: illegal label declaration`, ``)
 }
 
+func TestErrTplLit(t *testing.T) {
+	testErrCode(t, "tpl`a =`", `/foo/bar.gop:1:8: expected ';', found 'EOF' (and 1 more errors)`, ``)
+}
+
 func TestErrTuple(t *testing.T) {
 	testErrCode(t, `println (1,2)*2`, `/foo/bar.gop:1:9: tuple is not supported`, ``)
 	testErrCode(t, `println 2*(1,2)`, `/foo/bar.gop:1:13: expected ')', found ','`, ``)
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index 5db51079..f0de995a 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -29,6 +29,7 @@ import (
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
+	tpltoken "github.com/goplus/gop/tpl/token"
 )
 
 func sortedKeys(m any) []string {
@@ -47,6 +48,7 @@ var (
 	tyString    = reflect.TypeOf("")
 	tyToken     = reflect.TypeOf(token.Token(0))
 	tyObjectPtr = reflect.TypeOf((*ast.Object)(nil))
+	tplToken    = reflect.TypeOf(tpltoken.Token(0))
 )
 
 // FprintNode prints a Go+ AST node.
@@ -78,7 +80,7 @@ func FprintNode(w io.Writer, lead string, v any, prefix, indent string) {
 				sf := tyElem.Field(i)
 				sfv := elem.Field(i).Interface()
 				switch sf.Type {
-				case tyString, tyToken:
+				case tyString, tyToken, tplToken:
 					fmt.Fprintf(w, "%s%v: %v\n", prefix, sf.Name, sfv)
 				default:
 					FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent)