Repository: goplus/gop
Pull request #2069 (2025-01-11 11:40:44)

parser parseBranchStmt: allow goto as command name


Patch:
diff --git a/parser/parser.go b/parser/parser.go
index 3ba5f455..a9850690 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -2257,12 +2257,14 @@ func (p *parser) checkExprOrType(x ast.Expr) ast.Expr {
 }
 
 // If lhs is set and the result is an identifier, it is not resolved.
-func (p *parser) parsePrimaryExpr(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
+func (p *parser) parsePrimaryExpr(iden *ast.Ident, lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
 	if p.trace {
 		defer un(trace(p, "PrimaryExpr"))
 	}
 
-	if x, isTuple = p.parseOperand(lhs, allowTuple, allowCmd); isTuple {
+	if iden != nil {
+		x = iden
+	} else if x, isTuple = p.parseOperand(lhs, allowTuple, allowCmd); isTuple {
 		return
 	}
 L:
@@ -2384,7 +2386,7 @@ func (p *parser) checkCmd() bool {
 
 // parseErrWrapExpr: expr! expr? expr?:defval
 func (p *parser) parseErrWrapExpr(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
-	if x, isTuple = p.parsePrimaryExpr(lhs, allowTuple, allowCmd); isTuple {
+	if x, isTuple = p.parsePrimaryExpr(nil, lhs, allowTuple, allowCmd); isTuple {
 		return
 	}
 	if expr, ok := x.(*ast.ErrWrapExpr); ok {
@@ -2674,8 +2676,14 @@ const (
 // of a range clause (with mode == rangeOk). The returned statement is an
 // assignment with a right-hand side that is a single unary expression of
 // the form "range x". No guarantees are given for the left-hand side.
-func (p *parser) parseSimpleStmt(mode int, allowCmd bool) (ast.Stmt, bool) {
-	return p.parseSimpleStmtEx(mode, allowCmd, false)
+func (p *parser) parseSimpleStmt(mode int, allowCmd bool) ast.Stmt {
+	ss, _ /* isRange */ := p.parseSimpleStmtEx(mode, allowCmd, false)
+	return ss
+}
+
+func (p *parser) parseBranchCmdStmt(iden *ast.Ident) ast.Stmt { // Go+: goto as command
+	x, _ := p.parsePrimaryExpr(iden, false, false, true)
+	return &ast.ExprStmt{X: x}
 }
 
 func (p *parser) parseSimpleStmtEx(mode int, allowCmd, allowRangeExpr bool) (ast.Stmt, bool) {
@@ -2833,20 +2841,29 @@ func (p *parser) parseBranchStmt(tok token.Token) ast.Stmt {
 
 	oldpos, oldlit := p.pos, p.lit // Go+: save token to allow goto() as a function
 	pos := p.expect(tok)
-	if p.tok == token.LPAREN { // Go+: allow goto() as a function
+	next := p.tok
+	if next != token.IDENT && next != token.SEMICOLON { // Go+: allow goto() as a function
 		p.unget(oldpos, token.IDENT, oldlit)
-		s, _ := p.parseSimpleStmt(basic, false)
+		s := p.parseSimpleStmt(basic, true)
 		p.expectSemi()
 		return s
 	}
 
 	var label *ast.Ident
-	if tok != token.FALLTHROUGH && p.tok == token.IDENT {
+	if tok != token.FALLTHROUGH && next == token.IDENT {
 		label = p.parseIdent()
 		// add to list of unresolved targets
 		n := len(p.targetStack) - 1
 		p.targetStack[n] = append(p.targetStack[n], label)
 	}
+	if p.tok != token.SEMICOLON { // Go+: goto command
+		if label != nil {
+			p.unget(label.NamePos, token.IDENT, label.Name)
+		}
+		s := p.parseBranchCmdStmt(&ast.Ident{NamePos: oldpos, Name: oldlit})
+		p.expectSemi()
+		return s
+	}
 	p.expectSemi()
 
 	return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label}
@@ -2887,7 +2904,7 @@ func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
 			p.next()
 			p.error(p.pos, "var declaration not allowed in 'IF' initializer")
 		}
-		init, _ = p.parseSimpleStmt(basic, false)
+		init = p.parseSimpleStmt(basic, false)
 	}
 
 	var condStmt ast.Stmt
@@ -2904,7 +2921,7 @@ func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
 			p.expect(token.SEMICOLON)
 		}
 		if p.tok != token.LBRACE {
-			condStmt, _ = p.parseSimpleStmt(basic, false)
+			condStmt = p.parseSimpleStmt(basic, false)
 		}
 	} else {
 		condStmt = init
@@ -2951,7 +2968,7 @@ func (p *parser) parseForPhraseCond() (init ast.Stmt, cond ast.Expr) {
 			p.next()
 			p.error(p.pos, "var declaration not allowed in 'IF' initializer")
 		}
-		init, _ = p.parseSimpleStmt(basic, false)
+		init = p.parseSimpleStmt(basic, false)
 	}
 
 	var condStmt ast.Stmt
@@ -2968,7 +2985,7 @@ func (p *parser) parseForPhraseCond() (init ast.Stmt, cond ast.Expr) {
 			p.expect(token.SEMICOLON)
 		}
 		if !isForPhraseCondEnd(p.tok) {
-			condStmt, _ = p.parseSimpleStmt(basic, false)
+			condStmt = p.parseSimpleStmt(basic, false)
 		}
 	} else {
 		condStmt = init
@@ -3102,7 +3119,7 @@ func (p *parser) parseSwitchStmt() ast.Stmt {
 		prevLev := p.exprLev
 		p.exprLev = -1
 		if p.tok != token.SEMICOLON {
-			s2, _ = p.parseSimpleStmt(basic, false)
+			s2 = p.parseSimpleStmt(basic, false)
 		}
 		if p.tok == token.SEMICOLON {
 			p.next()
@@ -3123,7 +3140,7 @@ func (p *parser) parseSwitchStmt() ast.Stmt {
 				// Having the extra nested but empty scope won't affect it.
 				p.openScope()
 				defer p.closeScope()
-				s2, _ = p.parseSimpleStmt(basic, false)
+				s2 = p.parseSimpleStmt(basic, false)
 			}
 		}
 		p.exprLev = prevLev
@@ -3329,11 +3346,11 @@ func (p *parser) parseForStmt() ast.Stmt {
 			s1 = s2
 			s2 = nil
 			if p.tok != token.SEMICOLON {
-				s2, _ = p.parseSimpleStmt(basic, false)
+				s2 = p.parseSimpleStmt(basic, false)
 			}
 			p.expectSemi()
 			if p.tok != token.LBRACE {
-				s3, _ = p.parseSimpleStmt(basic, false)
+				s3 = p.parseSimpleStmt(basic, false)
 			}
 		}
 		p.exprLev = prevLev
@@ -3412,7 +3429,7 @@ func (p *parser) parseStmt(allowCmd bool) (s ast.Stmt) {
 		allowCmd = false
 		fallthrough
 	case token.IDENT, token.MAP: // operands
-		s, _ = p.parseSimpleStmt(labelOk, allowCmd)
+		s = p.parseSimpleStmt(labelOk, allowCmd)
 		// because of the required look-ahead, labeled statements are
 		// parsed by parseSimpleStmt - don't expect a semicolon after
 		// them
@@ -3684,7 +3701,7 @@ func (p *parser) parseOverloadFunc() (ast.Expr, bool) {
 	case token.FUNC:
 		return p.parseFuncTypeOrLit(), true
 	case token.LPAREN:
-		x, _ := p.parsePrimaryExpr(false, false, false)
+		x, _ := p.parsePrimaryExpr(nil, false, false, false)
 		return x, true
 	}
 	return nil, false


Test patch:
diff --git a/parser/_testdata/goto1/goto.gop b/parser/_testdata/goto1/goto.gop
new file mode 100644
index 00000000..1d7fac5a
--- /dev/null
+++ b/parser/_testdata/goto1/goto.gop
@@ -0,0 +1,3 @@
+goto "a"
+goto 1, 2
+goto (1+2)*3
diff --git a/parser/_testdata/goto1/parser.expect b/parser/_testdata/goto1/parser.expect
new file mode 100644
index 00000000..bb64e8d4
--- /dev/null
+++ b/parser/_testdata/goto1/parser.expect
@@ -0,0 +1,64 @@
+package main
+
+file goto.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 2
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BinaryExpr:
+                  X:
+                    ast.ParenExpr:
+                      X:
+                        ast.BinaryExpr:
+                          X:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 1
+                          Op: +
+                          Y:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 2
+                  Op: *
+                  Y:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 3
diff --git a/parser/_testdata/goto2/goto.gop b/parser/_testdata/goto2/goto.gop
new file mode 100644
index 00000000..a8f11b6f
--- /dev/null
+++ b/parser/_testdata/goto2/goto.gop
@@ -0,0 +1,2 @@
+goto x+y
+goto x, y
diff --git a/parser/_testdata/goto2/parser.expect b/parser/_testdata/goto2/parser.expect
new file mode 100644
index 00000000..4beebaff
--- /dev/null
+++ b/parser/_testdata/goto2/parser.expect
@@ -0,0 +1,41 @@
+package main
+
+file goto.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: x
+                  Op: +
+                  Y:
+                    ast.Ident:
+                      Name: y
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.Ident:
+                  Name: x
+                ast.Ident:
+                  Name: y