Repository: goplus/gop
Pull request #2247 (2025-04-01 17:58:30)

gopfullspec


Patch:
diff --git a/VERSION b/VERSION
deleted file mode 100644
index f995ee2e..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.3.7
diff --git a/cmd/chore/gopfullspec/fullspec.gop b/cmd/chore/gopfullspec/fullspec.gop
new file mode 100644
index 00000000..b05300ab
--- /dev/null
+++ b/cmd/chore/gopfullspec/fullspec.gop
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import (
+	"flag"
+	"gop/parser"
+	"gop/parser/parsertest"
+	"gop/token"
+	"gop/x/gopprojs"
+	"os"
+	"path/filepath"
+)
+
+func dump(f any) {
+	parsertest.FprintNode os.Stdout, "", f, "", "  "
+}
+
+func isGopFile(file string) bool {
+	if len(file) > 4 {
+		switch file[len(file)-4:] {
+		case ".gop", ".gox", ".gsh", ".spx", ".yap":
+			return true
+		}
+	}
+	return false
+}
+
+func parseFiles(files []string, dumpAST bool) (nErr int) {
+	fset := token.newFileSet
+	for file in files {
+		fprintln os.Stderr, "\n==> Parsing ${file}"
+		f, err := parser.parseFile(fset, file, nil, parser.ParseComments)
+		if err != nil {
+			fprintln os.Stderr, err
+			nErr++
+		} else if dumpAST {
+			dump f
+		}
+	}
+	return
+}
+
+func parseDir(dir string, recursively, dumpAST bool) (nErr int) {
+	fset := token.newFileSet
+	filepath.walkDir dir, (file, d, err) => {
+		if err != nil {
+			return err
+		} else if fname := d.name; d.isDir {
+			if recursively {
+				if fname.hasPrefix("_") || fname == "fullspec" {
+					return filepath.SkipDir
+				}
+			} else if file != dir {
+				return filepath.SkipDir
+			}
+		} else if !fname.hasPrefix("_") && isGopFile(fname) {
+			fprintln os.Stderr, "\n==> Parsing ${file}"
+			f, err := parser.parseFile(fset, file, nil, parser.ParseComments)
+			if err != nil {
+				fprintln os.Stderr, err
+				nErr++
+			} else if dumpAST {
+				dump f
+			}
+		}
+		return nil
+	}
+	return
+}
+
+var (
+	flagDumpAST = flag.Bool("ast", false, "dump AST")
+)
+
+flag.parse
+
+pattern := flag.args
+if pattern.len == 0 {
+	echo "Usage: gopfullspec [-ast] packages"
+	return
+}
+
+dumpAST := *flagDumpAST
+nErr := 0
+
+projs, err := gopprojs.parseAll(pattern...)
+if err != nil {
+	fprintln os.Stderr, err
+	os.exit 1
+}
+
+for proj in projs {
+	switch v := proj.(type) {
+	case *gopprojs.DirProj:
+		dir := v.Dir
+		recursively := dir.hasSuffix("/...")
+		if recursively {
+			dir = dir[:len(dir)-4]
+		}
+		nErr += parseDir(dir, recursively, dumpAST)
+	case *gopprojs.FilesProj:
+		nErr += parseFiles(v.Files, dumpAST)
+	default:
+		panic "only support local files and directories"
+	}
+}
+fprintln os.Stderr
+
+if nErr > 0 {
+	os.exit 1
+}
diff --git a/cmd/chore/gopminispec/minispec.gop b/cmd/chore/gopminispec/minispec.gop
index a674a437..71209ccf 100644
--- a/cmd/chore/gopminispec/minispec.gop
+++ b/cmd/chore/gopminispec/minispec.gop
@@ -84,7 +84,7 @@ flag.parse
 
 pattern := flag.args
 if pattern.len == 0 {
-	echo "Usage: gopminispec [-v] packages"
+	echo "Usage: gopminispec [-v -ast] packages"
 	return
 }
 
diff --git a/doc/spec/mini/mini.gop b/doc/spec/mini/mini.gop
index cb95d41f..bc501a7c 100644
--- a/doc/spec/mini/mini.gop
+++ b/doc/spec/mini/mini.gop
@@ -21,11 +21,6 @@ import (
 	"github.com/goplus/gop/tpl"
 )
 
-const (
-	domianTextQuote  = '`'
-	domianTextExpect = "expect domainTag`...`"
-)
-
 var _off = tpl.showConflict(false)
 
 var Spec = tpl`
@@ -109,7 +104,7 @@ LabeledStmt = IDENT ":" Statement
 
 SimpleStmt = SendStmt | ShortVarDecl | IncDecStmt | Assignment | ExpressionStmt | EmptyStmt
 
-SendStmt = IDENT ?("." IDENT) "<-" LambdaExprList
+SendStmt = IDENT ?("." IDENT) "<-" LambdaExprList ?"..."
 
 ShortVarDecl = IdentifierList ":=" ExpressionList
 
@@ -121,7 +116,7 @@ Assignment = ExpressionList (
 
 ExpressionStmt = Expression
 
-CommandStmt = IDENT ?("." IDENT) SPACE LambdaExprList
+CommandStmt = IDENT ?("." IDENT) SPACE LambdaExprList ?"..."
 
 EmptyStmt = ""
 
diff --git a/tpl/scanner/scanner.go b/tpl/scanner/scanner.go
index 32adb0e2..d3f20542 100644
--- a/tpl/scanner/scanner.go
+++ b/tpl/scanner/scanner.go
@@ -50,6 +50,7 @@ type Scanner struct {
 	offset     int  // character offset
 	rdOffset   int  // reading offset (position after current character)
 	lineOffset int  // current line offset
+	nParen     int
 	unitVal    string
 	insertSemi bool // insert a semicolon before next newline
 
@@ -712,6 +713,11 @@ func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Tok
 	return tok0
 }
 
+func (s *Scanner) tokSEMICOLON() token.Token {
+	s.nParen = 0
+	return token.SEMICOLON
+}
+
 // Scan scans the next token and returns the token position, the token,
 // and its literal string if applicable. The source end is indicated by
 // token.EOF.
@@ -769,7 +775,7 @@ scanAgain:
 		case -1:
 			if s.insertSemi {
 				s.insertSemi = false // EOF consumed
-				t.Tok, t.Lit = token.SEMICOLON, "\n"
+				t.Tok, t.Lit = s.tokSEMICOLON(), "\n"
 				return
 			}
 			t.Tok = token.EOF
@@ -778,7 +784,7 @@ scanAgain:
 			// set in the first place and exited early
 			// from s.skipWhitespace()
 			s.insertSemi = false // newline consumed
-			t.Tok, t.Lit = token.SEMICOLON, "\n"
+			t.Tok, t.Lit = s.tokSEMICOLON(), "\n"
 			return
 		case '"':
 			insertSemi = true
@@ -799,6 +805,9 @@ scanAgain:
 			if s.ch == '.' && s.peek() == '.' {
 				s.next()
 				s.next() // consume last '.'
+				if s.nParen == 0 {
+					insertSemi = true
+				}
 				t.Tok = token.ELLIPSIS
 			} else {
 				t.Tok = token.PERIOD
@@ -806,11 +815,13 @@ scanAgain:
 		case ',':
 			t.Tok = token.COMMA
 		case ';':
-			t.Tok = token.SEMICOLON
+			t.Tok = s.tokSEMICOLON()
 			t.Lit = ";"
 		case '(':
+			s.nParen++
 			t.Tok = token.LPAREN
 		case ')':
+			s.nParen--
 			insertSemi = true
 			t.Tok = token.RPAREN
 		case '[':
@@ -849,7 +860,7 @@ scanAgain:
 					s.offset = s.file.Offset(t.Pos)
 					s.rdOffset = s.offset + 1
 					s.insertSemi = false // newline consumed
-					t.Tok, t.Lit = token.SEMICOLON, "\n"
+					t.Tok, t.Lit = s.tokSEMICOLON(), "\n"
 					return
 				}
 				comment := s.scanComment()
@@ -869,7 +880,7 @@ scanAgain:
 				s.offset = s.file.Offset(t.Pos)
 				s.rdOffset = s.offset + 1
 				s.insertSemi = false
-				t.Tok, t.Lit = token.SEMICOLON, "\n"
+				t.Tok, t.Lit = s.tokSEMICOLON(), "\n"
 				return
 			}
 			comment := s.scanSharpComment()


Test patch:
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index f0de995a..d3bc6d1d 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -46,8 +46,10 @@ func sortedKeys(m any) []string {
 var (
 	tyNode      = reflect.TypeOf((*ast.Node)(nil)).Elem()
 	tyString    = reflect.TypeOf("")
+	tyBytes     = reflect.TypeOf([]byte(nil))
 	tyToken     = reflect.TypeOf(token.Token(0))
 	tyObjectPtr = reflect.TypeOf((*ast.Object)(nil))
+	tyScopePtr  = reflect.TypeOf((*ast.Scope)(nil))
 	tplToken    = reflect.TypeOf(tpltoken.Token(0))
 )
 
@@ -65,7 +67,7 @@ func FprintNode(w io.Writer, lead string, v any, prefix, indent string) {
 		}
 	case reflect.Ptr:
 		t := val.Type()
-		if val.IsNil() || t == tyObjectPtr {
+		if val.IsNil() || t == tyObjectPtr || t == tyScopePtr {
 			return
 		}
 		if t.Implements(tyNode) {
@@ -82,6 +84,7 @@ func FprintNode(w io.Writer, lead string, v any, prefix, indent string) {
 				switch sf.Type {
 				case tyString, tyToken, tplToken:
 					fmt.Fprintf(w, "%s%v: %v\n", prefix, sf.Name, sfv)
+				case tyBytes: // skip
 				default:
 					FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent)
 				}
diff --git a/tpl/scanner/_testdata/unit/tpl.expect b/tpl/scanner/_testdata/unit/tpl.expect
index 3688b4d5..b6b4ab2f 100644
--- a/tpl/scanner/_testdata/unit/tpl.expect
+++ b/tpl/scanner/_testdata/unit/tpl.expect
@@ -56,3 +56,5 @@
 152 IDENT echo
 157 STRING "Hello, world"
 171 ... 
+174 ; 
+