Repository: goplus/gop
Pull request #2191 (2025-03-24 15:14:40)

gop/tpl/parser.parseRule: RetProcParser


Patch:
diff --git a/parser/parser.go b/parser/parser.go
index cca9c479..6fe8a31d 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1714,7 +1714,7 @@ func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any {
 	file := p.file
 	base := file.Base()
 	src := p.scanner.CodeTo(int(end) - base)
-	expr, err := ParseExprEx(p.file, src, int(off)-base, 0)
+	expr, err := ParseExprEx(file, src, int(off)-base, 0)
 	if err != nil {
 		p.errors = append(p.errors, err...)
 		expr = &ast.BadExpr{From: off, To: end}
diff --git a/tpl/parser/parser.go b/tpl/parser/parser.go
index 3f725c47..4f728494 100644
--- a/tpl/parser/parser.go
+++ b/tpl/parser/parser.go
@@ -25,19 +25,26 @@ import (
 
 // -----------------------------------------------------------------------------
 
-// A Mode value is a set of flags (or 0).
-// They control the amount of source code parsed and other optional
-// parser functionality.
-type Mode uint
+// RetProcParser parses a RetProc.
+type RetProcParser = func(file *token.File, src []byte, offset int) (ast.Node, scanner.ErrorList)
+
+// Config configures the behavior of the parser.
+type Config struct {
+	ParseRetProc RetProcParser
+}
 
 // ParseFile parses a file and returns the AST.
-func ParseFile(fset *token.FileSet, filename string, src any, _ Mode) (f *ast.File, err error) {
+func ParseFile(fset *token.FileSet, filename string, src any, conf *Config) (f *ast.File, err error) {
 	b, err := iox.ReadSourceLocal(filename, src)
 	if err != nil {
 		return nil, err
 	}
+
 	var p parser
 	p.init(fset, filename, b)
+	if conf != nil {
+		p.parseRetProc = conf.ParseRetProc
+	}
 	f = p.parseFile()
 	switch p.errors.Len() {
 	case 0:
@@ -62,6 +69,9 @@ type parser struct {
 	tok token.Token
 	lit string
 
+	// Callback to parse RetProc.
+	parseRetProc RetProcParser
+
 	// Error handling
 	errors scanner.ErrorList
 }
@@ -161,7 +171,19 @@ func (p *parser) parseRule() *ast.Rule {
 
 	var retProc ast.Node
 	if p.tok == token.DRARROW { // => { ... }
-		panic("TODO: => { ... }")
+		if off, end, ok := p.lambdaExpr(); ok {
+			if p.parseRetProc != nil {
+				file := p.file
+				base := file.Base()
+				src := p.scanner.CodeTo(int(end) - base)
+				expr, err := p.parseRetProc(file, src, int(off)-base)
+				if err == nil {
+					retProc = expr
+				} else {
+					p.errors = append(p.errors, err...)
+				}
+			}
+		}
 	}
 
 	p.expect(token.SEMICOLON)
@@ -173,6 +195,29 @@ func (p *parser) parseRule() *ast.Rule {
 	}
 }
 
+func (p *parser) lambdaExpr() (start, end token.Pos, ok bool) {
+	start = p.pos // => {
+	p.next()
+	p.expect(token.LBRACE)
+	level := 1
+	for {
+		switch p.tok {
+		case token.RBRACE:
+			level--
+			if level == 0 { // }
+				p.next()
+				end, ok = p.pos, true
+				return
+			}
+		case token.LBRACE:
+			level++
+		case token.EOF:
+			return
+		}
+		p.next()
+	}
+}
+
 // parseExpr parses an expression: +term % '|'
 func (p *parser) parseExpr() ast.Expr {
 	termList := p.parseTermList()
diff --git a/tpl/scanner/scanner.go b/tpl/scanner/scanner.go
index 16087781..1d5376c3 100644
--- a/tpl/scanner/scanner.go
+++ b/tpl/scanner/scanner.go
@@ -142,6 +142,11 @@ func (s *Scanner) Init(file *token.File, src []byte, err ScanErrorHandler, mode
 	}
 }
 
+// CodeTo returns the source code snippet for the given end.
+func (s *Scanner) CodeTo(end int) []byte {
+	return s.src[:end]
+}
+
 func (s *Scanner) error(offs int, msg string) {
 	if s.err != nil {
 		s.err(s.file.Position(s.file.Pos(offs)), msg)
diff --git a/tpl/tpl.go b/tpl/tpl.go
index 03116520..712422a4 100644
--- a/tpl/tpl.go
+++ b/tpl/tpl.go
@@ -52,17 +52,20 @@ func FromFile(fset *token.FileSet, filename string, src any, params ...any) (ret
 	if fset == nil {
 		fset = token.NewFileSet()
 	}
-	f, err := parser.ParseFile(fset, filename, src, 0)
+	f, err := parser.ParseFile(fset, filename, src, nil)
 	if err != nil {
 		return
 	}
-	ret.Result, err = cl.New(fset, f)
+	ret.Result, err = cl.NewEx(retProcs(params), fset, f)
 	ret.Fset = fset
 	return
 }
 
 func retProcs(params []any) map[string]any {
 	n := len(params)
+	if n == 0 {
+		return nil
+	}
 	if n&1 != 0 {
 		panic("tpl.New: invalid params. should be in form `ruleName1, retProc1, ..., ruleNameN, retProcN`")
 	}


Test patch:
diff --git a/tpl/parser/_testdata/pseudo/in.gop b/tpl/parser/_testdata/pseudo/in.gop
new file mode 100644
index 00000000..cb76bc8d
--- /dev/null
+++ b/tpl/parser/_testdata/pseudo/in.gop
@@ -0,0 +1,123 @@
+file = stmts => {
+	return &ast.File{
+		Stmts: this.([]ast.Stmt),
+	}
+}
+
+stmts = *(stmt ";") => {
+	return [n.(ast.Stmt) for n in this]
+}
+
+stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt
+
+varStmt = "DECLARE" namelist ":" typeExpr => {
+	return &ast.DeclareStmt{
+		Declare: this[0].(*tpl.Token).Pos,
+		Names:   this[1].([]*ast.Ident),
+		Type:    this[3].(ast.Expr),
+	}
+}
+
+constStmt = "CONSTANT" IDENT "<-" expr => {
+	return &ast.ConstantStmt{
+		Constant: this[0].(*tpl.Token).Pos,
+		Name:     this[1].(*ast.Ident),
+		Value:    this[3].(ast.Expr),
+	}
+}
+
+assignStmt = IDENT "<-" expr => {
+	return &ast.AssignStmt{
+		Name:  this[0].(*ast.Ident),
+		Value: this[2].(ast.Expr),
+	}
+}
+
+outputStmt = "OUTPUT" exprlist => {
+	return &ast.OutputStmt{
+		Output: this[0].(*tpl.Token).Pos,
+		Values: this[1].([]ast.Expr),
+	}
+}
+
+inputStmt = "INPUT" namelist => {
+	return &ast.InputStmt{
+		Input: this[0].(*tpl.Token).Pos,
+		Names: this[1].([]*ast.Ident),
+	}
+}
+
+ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF" => {
+	var elseBody []ast.Stmt
+	if v := this[5]; v != nil {
+		elseStmt := v.([]any)
+		elseBody = elseStmt[2].([]ast.Stmt)
+	}
+	return &ast.IfStmt{
+		If:    this[0].(*tpl.Token).Pos,
+		Cond:  this[1].(ast.Expr),
+		Body:  this[4].([]ast.Stmt),
+		Else:  elseBody,
+		EndIf: this[6].(*tpl.Token).Pos,
+	}
+}
+
+whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE" => {
+	return &ast.WhileStmt{
+		While:    this[0].(*tpl.Token).Pos,
+		Cond:     this[1].(ast.Expr),
+		Body:     this[4].([]ast.Stmt),
+		EndWhile: this[5].(*tpl.Token).Pos,
+	}
+}
+
+untilStmt = "REPEAT" ";" stmts "UNTIL" expr => {
+	return &ast.UntilStmt{
+		Repeat: this[0].(*tpl.Token).Pos,
+		Body:   this[2].([]ast.Stmt),
+		Until:  this[3].(*tpl.Token).Pos,
+		Cond:   this[4].(ast.Expr),
+	}
+}
+
+typeExpr = "INTEGER" | "REAL" | "STRING" | "BOOLEAN" => {
+	return tpl.ident(this)
+}
+
+expr = binaryExpr2 % ("<" | "<=" | ">" | ">=" | "=" | "<>") => {
+	return tpl.binaryExpr(this)
+}
+
+binaryExpr2 = binaryExpr1 % ("+" | "-") => {
+	return tpl.binaryExpr(this)
+}
+
+binaryExpr1 = operand % ("*" | "/") => {
+	return tpl.binaryExpr(this)
+}
+
+operand = basicLit | ident | parenExpr | unaryExpr
+
+unaryExpr = "-" operand => {
+	return tpl.unaryExpr(this)
+}
+
+basicLit = INT | FLOAT | STRING => {
+	return tpl.basicLit(this)
+}
+
+ident = IDENT => {
+	return tpl.ident(this)
+}
+
+parenExpr = "(" expr ")" => {
+	return this[1]
+}
+
+exprlist = expr % "," => {
+	return [v.(ast.Expr) for v in tpl.list(this)]
+}
+
+namelist = IDENT % "," => {
+	return [tpl.ident(v) for v in tpl.list(this)]
+}
diff --git a/tpl/parser/_testdata/pseudo/out.expect b/tpl/parser/_testdata/pseudo/out.expect
new file mode 100644
index 00000000..d914ea78
--- /dev/null
+++ b/tpl/parser/_testdata/pseudo/out.expect
@@ -0,0 +1,374 @@
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: file
+  Expr:
+    ast.Ident:
+      Name: stmts
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: stmts
+  Expr:
+    ast.UnaryExpr:
+      Op: *
+      X:
+        ast.Sequence:
+          Items:
+            ast.Ident:
+              Name: stmt
+            ast.BasicLit:
+              Kind: STRING
+              Value: ";"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: stmt
+  Expr:
+    ast.Choice:
+      Options:
+        ast.Ident:
+          Name: varStmt
+        ast.Ident:
+          Name: constStmt
+        ast.Ident:
+          Name: outputStmt
+        ast.Ident:
+          Name: inputStmt
+        ast.Ident:
+          Name: ifStmt
+        ast.Ident:
+          Name: whileStmt
+        ast.Ident:
+          Name: untilStmt
+        ast.Ident:
+          Name: assignStmt
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: varStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "DECLARE"
+        ast.Ident:
+          Name: namelist
+        ast.BasicLit:
+          Kind: STRING
+          Value: ":"
+        ast.Ident:
+          Name: typeExpr
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: constStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "CONSTANT"
+        ast.Ident:
+          Name: IDENT
+        ast.BasicLit:
+          Kind: STRING
+          Value: "<-"
+        ast.Ident:
+          Name: expr
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: assignStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.Ident:
+          Name: IDENT
+        ast.BasicLit:
+          Kind: STRING
+          Value: "<-"
+        ast.Ident:
+          Name: expr
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: outputStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "OUTPUT"
+        ast.Ident:
+          Name: exprlist
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: inputStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "INPUT"
+        ast.Ident:
+          Name: namelist
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: ifStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "IF"
+        ast.Ident:
+          Name: expr
+        ast.BasicLit:
+          Kind: STRING
+          Value: "THEN"
+        ast.BasicLit:
+          Kind: STRING
+          Value: ";"
+        ast.Ident:
+          Name: stmts
+        ast.UnaryExpr:
+          Op: ?
+          X:
+            ast.Sequence:
+              Items:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "ELSE"
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: ";"
+                ast.Ident:
+                  Name: stmts
+        ast.BasicLit:
+          Kind: STRING
+          Value: "ENDIF"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: whileStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "WHILE"
+        ast.Ident:
+          Name: expr
+        ast.BasicLit:
+          Kind: STRING
+          Value: "DO"
+        ast.BasicLit:
+          Kind: STRING
+          Value: ";"
+        ast.Ident:
+          Name: stmts
+        ast.BasicLit:
+          Kind: STRING
+          Value: "ENDWHILE"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: untilStmt
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "REPEAT"
+        ast.BasicLit:
+          Kind: STRING
+          Value: ";"
+        ast.Ident:
+          Name: stmts
+        ast.BasicLit:
+          Kind: STRING
+          Value: "UNTIL"
+        ast.Ident:
+          Name: expr
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: typeExpr
+  Expr:
+    ast.Choice:
+      Options:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "INTEGER"
+        ast.BasicLit:
+          Kind: STRING
+          Value: "REAL"
+        ast.BasicLit:
+          Kind: STRING
+          Value: "STRING"
+        ast.BasicLit:
+          Kind: STRING
+          Value: "BOOLEAN"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: expr
+  Expr:
+    ast.BinaryExpr:
+      X:
+        ast.Ident:
+          Name: binaryExpr2
+      Op: %
+      Y:
+        ast.Choice:
+          Options:
+            ast.BasicLit:
+              Kind: STRING
+              Value: "<"
+            ast.BasicLit:
+              Kind: STRING
+              Value: "<="
+            ast.BasicLit:
+              Kind: STRING
+              Value: ">"
+            ast.BasicLit:
+              Kind: STRING
+              Value: ">="
+            ast.BasicLit:
+              Kind: STRING
+              Value: "="
+            ast.BasicLit:
+              Kind: STRING
+              Value: "<>"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: binaryExpr2
+  Expr:
+    ast.BinaryExpr:
+      X:
+        ast.Ident:
+          Name: binaryExpr1
+      Op: %
+      Y:
+        ast.Choice:
+          Options:
+            ast.BasicLit:
+              Kind: STRING
+              Value: "+"
+            ast.BasicLit:
+              Kind: STRING
+              Value: "-"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: binaryExpr1
+  Expr:
+    ast.BinaryExpr:
+      X:
+        ast.Ident:
+          Name: operand
+      Op: %
+      Y:
+        ast.Choice:
+          Options:
+            ast.BasicLit:
+              Kind: STRING
+              Value: "*"
+            ast.BasicLit:
+              Kind: STRING
+              Value: "/"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: operand
+  Expr:
+    ast.Choice:
+      Options:
+        ast.Ident:
+          Name: basicLit
+        ast.Ident:
+          Name: ident
+        ast.Ident:
+          Name: parenExpr
+        ast.Ident:
+          Name: unaryExpr
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: unaryExpr
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "-"
+        ast.Ident:
+          Name: operand
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: basicLit
+  Expr:
+    ast.Choice:
+      Options:
+        ast.Ident:
+          Name: INT
+        ast.Ident:
+          Name: FLOAT
+        ast.Ident:
+          Name: STRING
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: ident
+  Expr:
+    ast.Ident:
+      Name: IDENT
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: parenExpr
+  Expr:
+    ast.Sequence:
+      Items:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "("
+        ast.Ident:
+          Name: expr
+        ast.BasicLit:
+          Kind: STRING
+          Value: ")"
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: exprlist
+  Expr:
+    ast.BinaryExpr:
+      X:
+        ast.Ident:
+          Name: expr
+      Op: %
+      Y:
+        ast.BasicLit:
+          Kind: STRING
+          Value: ","
+ast.Rule:
+  Name:
+    ast.Ident:
+      Name: namelist
+  Expr:
+    ast.BinaryExpr:
+      X:
+        ast.Ident:
+          Name: IDENT
+      Op: %
+      Y:
+        ast.BasicLit:
+          Kind: STRING
+          Value: ","
diff --git a/tpl/parser/parser_test.go b/tpl/parser/parser_test.go
index 711ce09b..d49f2bf3 100644
--- a/tpl/parser/parser_test.go
+++ b/tpl/parser/parser_test.go
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package parser
+package parser_test
 
 import (
-	"go/scanner"
 	"log"
 	"os"
 	"path"
@@ -25,7 +24,11 @@ import (
 	"strings"
 	"testing"
 
+	gopp "github.com/goplus/gop/parser"
+	"github.com/goplus/gop/tpl/ast"
+	"github.com/goplus/gop/tpl/parser"
 	"github.com/goplus/gop/tpl/parser/parsertest"
+	"github.com/goplus/gop/tpl/scanner"
 	"github.com/goplus/gop/tpl/token"
 )
 
@@ -36,7 +39,11 @@ func testFrom(t *testing.T, pkgDir, sel string) {
 	t.Helper()
 	log.Println("Parsing", pkgDir)
 	fset := token.NewFileSet()
-	f, err := ParseFile(fset, pkgDir+"/in.gop", nil, 0)
+	f, err := parser.ParseFile(fset, pkgDir+"/in.gop", nil, &parser.Config{
+		ParseRetProc: func(file *token.File, src []byte, offset int) (ast.Node, scanner.ErrorList) {
+			return gopp.ParseExprEx(file, src, offset, 0)
+		},
+	})
 	if err != nil {
 		if errs, ok := err.(scanner.ErrorList); ok {
 			for _, e := range errs {
diff --git a/tpl/parser/parsertest/parsertest.go b/tpl/parser/parsertest/parsertest.go
index dcf1c25a..8d1dd68e 100644
--- a/tpl/parser/parsertest/parsertest.go
+++ b/tpl/parser/parsertest/parsertest.go
@@ -62,6 +62,9 @@ func FprintNode(w io.Writer, lead string, v any, prefix, indent string) {
 			prefix += indent
 			for i := 0; i < n; i++ {
 				sf := tyElem.Field(i)
+				if sf.Name == "RetProc" { // skip RetProc field, see gop/tpl/ast.Rule
+					continue
+				}
 				sfv := elem.Field(i).Interface()
 				switch sf.Type {
 				case tyString, tyToken: