Repository: goplus/gop
Pull request #2114 (2025-02-22 01:17:14)

v1.3.1


Patch:
diff --git a/README.md b/README.md
index 09712e05..10ca954c 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ Code style is just the first step. We have made many efforts to make the code mo
 | fmt.Println("Hi") | echo("Hi") | More builtin functions: It simplifies the expression of the most common tasks |
 | fmt.Println("Hi") | echo "Hi" | Command-line style code: It reduces the number of parentheses in the code as much as possible, making it closer to natural language |
 | a := []int{1, 2, 3} | a := [1, 2, 3] | List literals |
+| a = append(a, 4)<br>a = append(a, 5, 6, 7) | a <- 4<br>a <- 5, 6, 7 | Append values to a list |
 | a := map[string]int{<br>&nbsp;&nbsp;&nbsp;&nbsp;"Monday": 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;"Tuesday": 2,<br>} | a := {<br>&nbsp;&nbsp;&nbsp;&nbsp;"Monday": 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;"Tuesday": 2,<br>} | Mapping literals |
 | OnStart(func() {<br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>}) | onStart => {<br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>} | Lambda expressions |
 | type Rect struct {<br>&nbsp;&nbsp;&nbsp;&nbsp;Width&nbsp; float64<br>&nbsp;&nbsp;&nbsp;&nbsp;Height float64<br>}<br><br>func (this *Rect) Area() float64 { <br>&nbsp;&nbsp;&nbsp;&nbsp;return this.Width * this.Height<br>} | var (<br>&nbsp;&nbsp;&nbsp;&nbsp;Width&nbsp; float64<br>&nbsp;&nbsp;&nbsp;&nbsp;Height float64<br>)<br><br>func Area() float64 { <br>&nbsp;&nbsp;&nbsp;&nbsp;return Width * Height<br>} | [Go+ Classfiles](doc/classfile.md): We can express OOP with global variables and functions. |
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 18fa8e74..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.3.0
diff --git a/ast/ast.go b/ast/ast.go
index 1a87ed9b..5496a164 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -604,13 +604,6 @@ type (
 		X Expr // expression
 	}
 
-	// A SendStmt node represents a send statement.
-	SendStmt struct {
-		Chan  Expr
-		Arrow token.Pos // position of "<-"
-		Value Expr
-	}
-
 	// An IncDecStmt node represents an increment or decrement statement.
 	IncDecStmt struct {
 		X      Expr
@@ -815,9 +808,6 @@ func (s *LabeledStmt) End() token.Pos { return s.Stmt.End() }
 // End returns position of first character immediately after the node.
 func (s *ExprStmt) End() token.Pos { return s.X.End() }
 
-// End returns position of first character immediately after the node.
-func (s *SendStmt) End() token.Pos { return s.Value.End() }
-
 // End returns position of first character immediately after the node.
 func (s *IncDecStmt) End() token.Pos {
 	return s.TokPos + 2 /* len("++") */
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 87e86b6a..64fe4a85 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -282,6 +282,41 @@ func (*LambdaExpr2) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
+// A RangeExpr node represents a range expression.
+type RangeExpr struct {
+	First  Expr      // start of composite elements; or nil
+	To     token.Pos // position of ":"
+	Last   Expr      // end of composite elements
+	Colon2 token.Pos // position of ":" or token.NoPos
+	Expr3  Expr      // step (or max) of composite elements; or nil
+}
+
+// Pos - position of first character belonging to the node.
+func (p *RangeExpr) Pos() token.Pos {
+	if p.First != nil {
+		return p.First.Pos()
+	}
+	return p.To
+}
+
+// End - position of first character immediately after the node.
+func (p *RangeExpr) End() token.Pos {
+	if p.Expr3 != nil {
+		return p.Expr3.End()
+	}
+	if p.Colon2 != token.NoPos {
+		return p.Colon2 + 1
+	}
+	if p.Last != nil {
+		return p.Last.End()
+	}
+	return p.To + 1
+}
+
+func (*RangeExpr) exprNode() {}
+
+// -----------------------------------------------------------------------------
+
 // ForPhrase represents `for k, v <- container if init; cond` phrase.
 type ForPhrase struct {
 	For        token.Pos // position of "for" keyword
@@ -354,37 +389,21 @@ func (*ForPhraseStmt) stmtNode() {}
 
 // -----------------------------------------------------------------------------
 
-// A RangeExpr node represents a range expression.
-type RangeExpr struct {
-	First  Expr      // start of composite elements; or nil
-	To     token.Pos // position of ":"
-	Last   Expr      // end of composite elements
-	Colon2 token.Pos // position of ":" or token.NoPos
-	Expr3  Expr      // step (or max) of composite elements; or nil
-}
-
-// Pos - position of first character belonging to the node.
-func (p *RangeExpr) Pos() token.Pos {
-	if p.First != nil {
-		return p.First.Pos()
-	}
-	return p.To
+// A SendStmt node represents a send statement.
+type SendStmt struct {
+	Chan     Expr
+	Arrow    token.Pos // position of "<-"
+	Values   []Expr    // len(Values) must > 0
+	Ellipsis token.Pos // position of "..."
 }
 
-// End - position of first character immediately after the node.
-func (p *RangeExpr) End() token.Pos {
-	if p.Expr3 != nil {
-		return p.Expr3.End()
-	}
-	if p.Colon2 != token.NoPos {
-		return p.Colon2 + 1
-	}
-	if p.Last != nil {
-		return p.Last.End()
+// End returns position of first character immediately after the node.
+func (s *SendStmt) End() token.Pos {
+	if s.Ellipsis != token.NoPos {
+		return s.Ellipsis + 3
 	}
-	return p.To + 1
+	vals := s.Values
+	return vals[len(vals)-1].End()
 }
 
-func (*RangeExpr) exprNode() {}
-
 // -----------------------------------------------------------------------------
diff --git a/ast/walk.go b/ast/walk.go
index 0cea28fe..88a21fd5 100644
--- a/ast/walk.go
+++ b/ast/walk.go
@@ -223,7 +223,9 @@ func Walk(v Visitor, node Node) {
 
 	case *SendStmt:
 		Walk(v, n.Chan)
-		Walk(v, n.Value)
+		for _, val := range n.Values {
+			Walk(v, val)
+		}
 
 	case *IncDecStmt:
 		Walk(v, n.X)
diff --git a/cl/stmt.go b/cl/stmt.go
index b2d4ff12..48d92bb0 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -260,19 +260,25 @@ func isAppendable(x ast.Expr) bool {
 
 func compileSendStmt(ctx *blockCtx, expr *ast.SendStmt) {
 	cb := ctx.cb
-	ch, v := expr.Chan, expr.Value
+	ch, vals := expr.Chan, expr.Values
 	stk := cb.InternalStack()
-	if isAppendable(ch) { // a <- v (issue #2107)
+	if isAppendable(ch) { // a <- v1, v2, v3 (issue #2107)
 		compileExpr(ctx, ch)
 		a := stk.Get(-1)
 		t := a.Type.Underlying()
-		if _, ok := t.(*types.Slice); ok { // a = append(a, x)
+		if _, ok := t.(*types.Slice); ok { // a = append(a, v1, v2, v3)
 			stk.Pop()
 			compileExprLHS(ctx, ch)
 			cb.Val(ctx.pkg.Builtin().Ref("append"))
 			stk.Push(a)
-			compileExpr(ctx, v)
-			cb.CallWith(2, 0, expr).AssignWith(1, 1, expr)
+			for _, v := range vals {
+				compileExpr(ctx, v)
+			}
+			flags := gogen.InstrFlags(0)
+			if expr.Ellipsis != 0 { // a = append(a, b...)
+				flags |= gogen.InstrFlagEllipsis
+			}
+			cb.CallWith(len(vals)+1, flags, expr).AssignWith(1, 1, expr)
 			return
 		}
 		goto normal
@@ -280,7 +286,10 @@ func compileSendStmt(ctx *blockCtx, expr *ast.SendStmt) {
 	compileExpr(ctx, ch)
 
 normal:
-	compileExpr(ctx, v)
+	if len(vals) != 1 || expr.Ellipsis != 0 {
+		panic(ctx.newCodeError(vals[0].End(), "can't send multiple values to a channel"))
+	}
+	compileExpr(ctx, vals[0])
 	ctx.cb.Send()
 }
 
diff --git a/parser/parser.go b/parser/parser.go
index a9850690..dd7110dc 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -2762,8 +2762,19 @@ func (p *parser) parseSimpleStmtEx(mode int, allowCmd, allowRangeExpr bool) (ast
 		// send statement
 		arrow := p.pos
 		p.next()
-		y := p.parseRHS()
-		return &ast.SendStmt{Chan: x[0], Arrow: arrow, Value: y}, false
+		var ellipsis token.Pos
+		vals := make([]ast.Expr, 1)
+		vals[0] = p.parseRHS()
+		if p.tok == token.ELLIPSIS { // a <- v... (issues #2107)
+			ellipsis = p.pos
+			p.next()
+		} else {
+			for p.tok == token.COMMA { // a <- v1, v2, v3 (issues #2107)
+				p.next()
+				vals = append(vals, p.parseRHS())
+			}
+		}
+		return &ast.SendStmt{Chan: x[0], Arrow: arrow, Values: vals, Ellipsis: ellipsis}, false
 
 	case token.INC, token.DEC:
 		// increment or decrement
@@ -3183,7 +3194,7 @@ func (p *parser) parseCommClause() *ast.CommClause {
 			arrow := p.pos
 			p.next()
 			rhs := p.parseRHS()
-			comm = &ast.SendStmt{Chan: lhs[0], Arrow: arrow, Value: rhs}
+			comm = &ast.SendStmt{Chan: lhs[0], Arrow: arrow, Values: []ast.Expr{rhs}}
 		} else {
 			// RecvStmt
 			if tok := p.tok; tok == token.ASSIGN || tok == token.DEFINE {
diff --git a/printer/nodes.go b/printer/nodes.go
index 7e7c2a08..9899f5ca 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -1402,7 +1402,15 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) {
 		const depth = 1
 		p.expr0(s.Chan, depth)
 		p.print(blank, s.Arrow, token.ARROW, blank)
-		p.expr0(s.Value, depth)
+		for i, val := range s.Values {
+			if i > 0 {
+				p.print(token.COMMA, blank)
+			}
+			p.expr0(val, depth)
+		}
+		if s.Ellipsis.IsValid() {
+			p.print(s.Ellipsis, token.ELLIPSIS)
+		}
 
 	case *ast.IncDecStmt:
 		const depth = 1
diff --git a/x/format/stmt_expr_or_type.go b/x/format/stmt_expr_or_type.go
index b5e13c65..d8fc5d89 100644
--- a/x/format/stmt_expr_or_type.go
+++ b/x/format/stmt_expr_or_type.go
@@ -248,7 +248,9 @@ func formatStmt(ctx *formatCtx, stmt ast.Stmt) {
 		formatCallExpr(ctx, v.Call)
 	case *ast.SendStmt:
 		formatExpr(ctx, v.Chan, &v.Chan)
-		formatExpr(ctx, v.Value, &v.Value)
+		for i, val := range v.Values {
+			formatExpr(ctx, val, &v.Values[i])
+		}
 	case *ast.LabeledStmt:
 		formatStmt(ctx, v.Stmt)
 	case *ast.BranchStmt, *ast.EmptyStmt, nil, *ast.BadStmt:


Test patch:
diff --git a/cl/_testgop/append/in.gop b/cl/_testgop/append1/in.gop
similarity index 100%
rename from cl/_testgop/append/in.gop
rename to cl/_testgop/append1/in.gop
diff --git a/cl/_testgop/append/out.go b/cl/_testgop/append1/out.go
similarity index 100%
rename from cl/_testgop/append/out.go
rename to cl/_testgop/append1/out.go
diff --git a/cl/_testgop/append2/in.gop b/cl/_testgop/append2/in.gop
new file mode 100644
index 00000000..39d9a2b9
--- /dev/null
+++ b/cl/_testgop/append2/in.gop
@@ -0,0 +1,7 @@
+a := [1, 2, 3]
+a <- 4, 5
+echo a
+
+b := [7, 8]
+a <- b...
+echo a
diff --git a/cl/_testgop/append2/out.go b/cl/_testgop/append2/out.go
new file mode 100644
index 00000000..238884c8
--- /dev/null
+++ b/cl/_testgop/append2/out.go
@@ -0,0 +1,12 @@
+package main
+
+import "fmt"
+
+func main() {
+	a := []int{1, 2, 3}
+	a = append(a, 4, 5)
+	fmt.Println(a)
+	b := []int{7, 8}
+	a = append(a, b...)
+	fmt.Println(a)
+}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 6341a700..a9553ee3 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -37,6 +37,13 @@ func codeErrorTestAst(t *testing.T, pkgname, filename, msg, src string) {
 	cltest.ErrorAst(t, pkgname, filename, msg, src)
 }
 
+func TestErrSendStmt(t *testing.T) {
+	codeErrorTest(t, `bar.gop:3:8: can't send multiple values to a channel`, `
+	var a chan int
+	a <- 1, 2
+`)
+}
+
 func TestErrVargCommand(t *testing.T) {
 	codeErrorTest(t, `bar.gop:5:1: not enough arguments in call to Ls
 	have ()
diff --git a/parser/_testdata/append1/append.gop b/parser/_testdata/append1/append.gop
new file mode 100644
index 00000000..dc89bb7d
--- /dev/null
+++ b/parser/_testdata/append1/append.gop
@@ -0,0 +1,1 @@
+a <- 1, 2, 3
diff --git a/parser/_testdata/append1/parser.expect b/parser/_testdata/append1/parser.expect
new file mode 100644
index 00000000..2cc8e03c
--- /dev/null
+++ b/parser/_testdata/append1/parser.expect
@@ -0,0 +1,29 @@
+package main
+
+file append.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.SendStmt:
+          Chan:
+            ast.Ident:
+              Name: a
+          Values:
+            ast.BasicLit:
+              Kind: INT
+              Value: 1
+            ast.BasicLit:
+              Kind: INT
+              Value: 2
+            ast.BasicLit:
+              Kind: INT
+              Value: 3
diff --git a/parser/_testdata/append2/append.gop b/parser/_testdata/append2/append.gop
new file mode 100644
index 00000000..492ca1b6
--- /dev/null
+++ b/parser/_testdata/append2/append.gop
@@ -0,0 +1,1 @@
+a <- b...
diff --git a/parser/_testdata/append2/parser.expect b/parser/_testdata/append2/parser.expect
new file mode 100644
index 00000000..9f7f4b5d
--- /dev/null
+++ b/parser/_testdata/append2/parser.expect
@@ -0,0 +1,22 @@
+package main
+
+file append.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.SendStmt:
+          Chan:
+            ast.Ident:
+              Name: a
+          Values:
+            ast.Ident:
+              Name: b
diff --git a/parser/_testdata/functype/parser.expect b/parser/_testdata/functype/parser.expect
index 9d588e2b..182dd084 100644
--- a/parser/_testdata/functype/parser.expect
+++ b/parser/_testdata/functype/parser.expect
@@ -68,7 +68,7 @@ ast.FuncDecl:
           Chan:
             ast.Ident:
               Name: v
-          Value:
+          Values:
             ast.Ident:
               Name: true
         ast.ExprStmt:
diff --git a/parser/_testdata/mytest/parser.expect b/parser/_testdata/mytest/parser.expect
index 8cfb4a85..e4978817 100644
--- a/parser/_testdata/mytest/parser.expect
+++ b/parser/_testdata/mytest/parser.expect
@@ -554,7 +554,7 @@ ast.FuncDecl:
                       Chan:
                         ast.Ident:
                           Name: ch
-                      Value:
+                      Values:
                         ast.Ident:
                           Name: true
                   Body:
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 2277ec8d..086a27fc 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -276,7 +276,7 @@ ast.FuncDecl:
                       Chan:
                         ast.Ident:
                           Name: c
-                      Value:
+                      Values:
                         ast.Ident:
                           Name: true
                 ast.CommClause: