Repository: goplus/gop
Pull request #2190 (2025-03-24 11:57:05)

gop/parser: ParseExprEx


Patch:
diff --git a/parser/interface.go b/parser/interface.go
index a798f60f..e8f5b456 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -165,7 +165,7 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex
 	return
 }
 
-// parseExprEx is a convenience function for parsing an expression.
+// ParseExprEx is a convenience function for parsing an expression.
 // The arguments have the same meaning as for ParseFile, but the source must
 // be a valid Go/Go+ (type or value) expression. Specifically, fset must not
 // be nil.
@@ -175,7 +175,7 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex
 // errors were found, the result is a partial AST (with ast.Bad* nodes
 // representing the fragments of erroneous source code). Multiple errors
 // are returned via a scanner.ErrorList which is sorted by source position.
-func parseExprEx(file *token.File, src []byte, offset int, mode Mode) (expr ast.Expr, err scanner.ErrorList) {
+func ParseExprEx(file *token.File, src []byte, offset int, mode Mode) (expr ast.Expr, err scanner.ErrorList) {
 	var p parser
 	defer func() {
 		if e := recover(); e != nil {
diff --git a/parser/parser.go b/parser/parser.go
index 3f25cfac..cca9c479 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(p.file, src, int(off)-base, 0)
 	if err != nil {
 		p.errors = append(p.errors, err...)
 		expr = &ast.BadExpr{From: off, To: end}


Test patch:
diff --git a/parser/_testexpr/lambda/in.gop b/parser/_testexpr/lambda/in.gop
new file mode 100644
index 00000000..012ce3a1
--- /dev/null
+++ b/parser/_testexpr/lambda/in.gop
@@ -0,0 +1,3 @@
+=> {
+    return this
+}
diff --git a/parser/_testexpr/lambda/out.expect b/parser/_testexpr/lambda/out.expect
new file mode 100644
index 00000000..392629fa
--- /dev/null
+++ b/parser/_testexpr/lambda/out.expect
@@ -0,0 +1,8 @@
+ast.LambdaExpr2:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ReturnStmt:
+          Results:
+            ast.Ident:
+              Name: this
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index a49f2487..697a2129 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -125,6 +125,35 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
 	}
 }
 
+func testExprFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
+	if sel != "" && !strings.Contains(pkgDir, sel) {
+		return
+	}
+	t.Helper()
+	log.Println("Parsing", pkgDir)
+	fset := token.NewFileSet()
+	fname := pkgDir + "/in.gop"
+	src, err := os.ReadFile(fname)
+	if err != nil {
+		t.Fatal("os.ReadFile:", err)
+	}
+	file := fset.AddFile(fname, -1, len(src))
+	expr, errs := ParseExprEx(file, src, 0, (Trace|ParseComments)&^exclude)
+	if errs != nil {
+		if len(errs) > 0 {
+			for _, e := range errs {
+				t.Log(e)
+			}
+		}
+		t.Fatal("ParseExprEx failed:", errs)
+	}
+	b, err := os.ReadFile(pkgDir + "/out.expect")
+	if err != nil {
+		t.Fatal("Parsing", pkgDir, "-", err)
+	}
+	parsertest.ExpectNode(t, expr, string(b))
+}
+
 func TestParseGo(t *testing.T) {
 	fset := token.NewFileSet()
 	pkgs, err := ParseDirEx(fset, "./_testdata/functype", Config{Mode: Trace | ParseGoAsGoPlus})
@@ -341,7 +370,7 @@ func TestErrParse(t *testing.T) {
 	}
 }
 
-func testFromDir(t *testing.T, sel, relDir string) {
+func testFromDir(t *testing.T, sel, relDir string, testExpr ...bool) {
 	dir, err := os.Getwd()
 	if err != nil {
 		t.Fatal("Getwd failed:", err)
@@ -351,17 +380,27 @@ func testFromDir(t *testing.T, sel, relDir string) {
 	if err != nil {
 		t.Fatal("ReadDir failed:", err)
 	}
+	isTestExpr := len(testExpr) > 0 && testExpr[0]
 	for _, fi := range fis {
 		name := fi.Name()
 		if strings.HasPrefix(name, "_") {
 			continue
 		}
 		t.Run(name, func(t *testing.T) {
-			testFrom(t, dir+"/"+name, sel, 0)
+			pkgDir := dir + "/" + name
+			if isTestExpr {
+				testExprFrom(t, pkgDir, sel, 0)
+			} else {
+				testFrom(t, pkgDir, sel, 0)
+			}
 		})
 	}
 }
 
+func TestFromTestexpr(t *testing.T) {
+	testFromDir(t, "", "./_testexpr", true)
+}
+
 func TestFromTestdata(t *testing.T) {
 	testFromDir(t, "", "./_testdata")
 }
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index ac94bb9f..5db51079 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -121,7 +121,7 @@ func Fprint(w io.Writer, pkg *ast.Package) {
 	}
 }
 
-// Expect asserts a Go+ package AST equals output or not.
+// Expect asserts a Go+ AST package equals output or not.
 func Expect(t *testing.T, pkg *ast.Package, expected string) {
 	b := bytes.NewBuffer(nil)
 	Fprint(b, pkg)
@@ -131,3 +131,14 @@ func Expect(t *testing.T, pkg *ast.Package, expected string) {
 		t.Fatal("gop.Parser: unexpect result")
 	}
 }
+
+// ExpectNode asserts a Go+ AST node equals output or not.
+func ExpectNode(t *testing.T, node any, expected string) {
+	b := bytes.NewBuffer(nil)
+	FprintNode(b, "", node, "", "  ")
+	output := b.String()
+	if expected != output {
+		fmt.Fprint(os.Stderr, output)
+		t.Fatal("gop.Parser: unexpect result")
+	}
+}