Repository: goplus/gop
Pull request #1806 (2024-03-09 10:47:45)

parse EnvExpr


Patch:
diff --git a/.github/workflows/check_goreleaser_config.py b/.github/workflows/check_goreleaser_config.py
deleted file mode 100644
index ecd88844..00000000
--- a/.github/workflows/check_goreleaser_config.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import os
-import subprocess
-import yaml
-
-files = [f for f in os.listdir('.') if not f.startswith(
-    ".") and f not in ["VERSION"]]
-files.sort()
-# filter out the files that are ignored by git
-files = [f for f in files if subprocess.call(
-    ["git", "ls-files", "--error-unmatch", f],
-    stdout=subprocess.DEVNULL,
-    stderr=subprocess.DEVNULL) == 0]
-
-gorel = yaml.load(open(".goreleaser.yaml", "r"), Loader=yaml.FullLoader)
-scfiles = [f["source"] for f in gorel["snapcrafts"][0]["extra_files"]]
-scfiles.sort()
-
-failed = False
-
-if files != scfiles:
-    failed = True
-    print("Files in snapcraft are different from the ones in the repo")
-    print("Update .goreleaser.yaml in the snapcraft section:")
-    for f in files:
-        print(f"      - source: \"{f}\"")
-        print(f"        destination: \"{f}\"")
-
-nfpms_files = [f["src"]
-               for f in gorel["nfpms"][0]["contents"] if f.get("type") != "symlink"]
-nfpms_files.sort()
-
-if files != nfpms_files:
-    failed = True
-    print("Files in nfpms are different from the ones in the repo")
-    print("Update .goreleaser.yaml in the nfpms section:")
-    for f in files:
-        print(f"      - src: \"{f}\"")
-        print(f"        dst: \"/usr/lib/{{{{ .ProjectName }}}}/{f}\"")
-
-if failed:
-    exit(1)
-
-print(".goreleaser checks passed")
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 76788ece..18c6fb3a 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -7,15 +7,6 @@ on:
     branches: [ main ]
 
 jobs:
-  Check:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - name: Check goreleaser files
-      run: |
-        pip install --no-input pyyaml
-        python .github/workflows/check_goreleaser_config.py
-
   Test:
     strategy:
       matrix:
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index b02c086e..6e6e4154 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -12,11 +12,6 @@ jobs:
     - uses: actions/checkout@v4
       with:
         fetch-depth: 0
-    
-    - name: Check goreleaser files
-      run: |
-        pip install --no-input pyyaml
-        python .github/workflows/check_goreleaser_config.py
 
     - name: Install Snapcraft
       uses: samuelmeuli/action-snapcraft@v2
@@ -34,11 +29,6 @@ jobs:
         tag=$(git describe --tags --dirty)
         version=$(cat VERSION)
         if [ "$tag" != "$version" ]; then
-          # if tag is vX.Y.Z-pN and version is vX.Y.Z then it's ok
-          if [[ $tag == $version-p* ]] && [[ ${tag#"$version"} =~ ^-p[0-9]+$ ]] then
-            echo "This is a patch version"
-            exit 0
-          fi
           echo "VERSION file is not consistent with tag name"
           echo "VERSION: $version"
           echo "TAG: $tag"
@@ -74,7 +64,7 @@ jobs:
     - name: Upload deb/rpm to Fury.io
       if: env.IS_PRERELEASE != 'true'
       run: |
-        for file in .dist/*.{deb,rpm}
+        for file in dist/*.{deb,rpm}
         do
           echo "Uploading $file to Fury.io"
           CODE=`curl --write-out '%{http_code}' --output /dev/null -sS -F package=@$file https://$FURY_TOKEN@push.fury.io/$GITHUB_REPOSITORY_OWNER/`
diff --git a/.gitignore b/.gitignore
index 29fa5fdb..4619940e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,4 +50,4 @@ bin/
 go-num/
 _todo*.go
 
-.dist/
+dist/
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 056f1ef7..cffa2061 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -4,7 +4,6 @@
 # vim: set ts=2 sw=2 tw=0 fo=cnqoj
 
 version: 1
-dist: ".dist"
 
 before:
   hooks:
@@ -147,66 +146,42 @@ nfpms:
     bindir: /usr/lib/{{ .ProjectName }}
     contents:
       # source folder
-      - src: "LICENSE"
-        dst: "/usr/lib/{{ .ProjectName }}/LICENSE"
-      - src: "Makefile"
-        dst: "/usr/lib/{{ .ProjectName }}/Makefile"
+      - src: LICENSE
+        dst: /usr/lib/{{ .ProjectName }}/LICENSE
       - src: "README.md"
-        dst: "/usr/lib/{{ .ProjectName }}/README.md"
-      - src: "_gendeps.go"
-        dst: "/usr/lib/{{ .ProjectName }}/_gendeps.go"
-      - src: "all.bash"
-        dst: "/usr/lib/{{ .ProjectName }}/all.bash"
-      - src: "all.bat"
-        dst: "/usr/lib/{{ .ProjectName }}/all.bat"
-      - src: "ast"
-        dst: "/usr/lib/{{ .ProjectName }}/ast"
-      - src: "build_install_run.go"
-        dst: "/usr/lib/{{ .ProjectName }}/build_install_run.go"
-      - src: "builtin"
-        dst: "/usr/lib/{{ .ProjectName }}/builtin"
-      - src: "cl"
-        dst: "/usr/lib/{{ .ProjectName }}/cl"
-      - src: "cmd"
-        dst: "/usr/lib/{{ .ProjectName }}/cmd"
-      - src: "doc"
-        dst: "/usr/lib/{{ .ProjectName }}/doc"
-      - src: "env"
-        dst: "/usr/lib/{{ .ProjectName }}/env"
-      - src: "format"
-        dst: "/usr/lib/{{ .ProjectName }}/format"
-      - src: "gengo.go"
-        dst: "/usr/lib/{{ .ProjectName }}/gengo.go"
+        dst: /usr/lib/{{ .ProjectName }}/README.md
       - src: "go.mod"
-        dst: "/usr/lib/{{ .ProjectName }}/go.mod"
+        dst: /usr/lib/{{ .ProjectName }}/go.mod
       - src: "go.sum"
-        dst: "/usr/lib/{{ .ProjectName }}/go.sum"
-      - src: "imp.go"
-        dst: "/usr/lib/{{ .ProjectName }}/imp.go"
-      - src: "load.go"
-        dst: "/usr/lib/{{ .ProjectName }}/load.go"
-      - src: "make.bash"
-        dst: "/usr/lib/{{ .ProjectName }}/make.bash"
-      - src: "make.bat"
-        dst: "/usr/lib/{{ .ProjectName }}/make.bat"
-      - src: "outline.go"
-        dst: "/usr/lib/{{ .ProjectName }}/outline.go"
-      - src: "parser"
-        dst: "/usr/lib/{{ .ProjectName }}/parser"
-      - src: "printer"
-        dst: "/usr/lib/{{ .ProjectName }}/printer"
-      - src: "scanner"
-        dst: "/usr/lib/{{ .ProjectName }}/scanner"
-      - src: "test"
-        dst: "/usr/lib/{{ .ProjectName }}/test"
-      - src: "testdata"
-        dst: "/usr/lib/{{ .ProjectName }}/testdata"
-      - src: "tidy.go"
-        dst: "/usr/lib/{{ .ProjectName }}/tidy.go"
-      - src: "token"
-        dst: "/usr/lib/{{ .ProjectName }}/token"
+        dst: /usr/lib/{{ .ProjectName }}/go.sum
+      - src: "*.go"
+        dst: /usr/lib/{{ .ProjectName }}/
+      - src: ast
+        dst: /usr/lib/{{ .ProjectName }}/ast
+      - src: builtin
+        dst: /usr/lib/{{ .ProjectName }}/builtin
+      - src: cl
+        dst: /usr/lib/{{ .ProjectName }}/cl
+      - src: cmd
+        dst: /usr/lib/{{ .ProjectName }}/cmd
+      - src: doc
+        dst: /usr/lib/{{ .ProjectName }}/doc
+      - src: env
+        dst: /usr/lib/{{ .ProjectName }}/env
+      - src: format
+        dst: /usr/lib/{{ .ProjectName }}/format
+      - src: parser
+        dst: /usr/lib/{{ .ProjectName }}/parser
+      - src: printer
+        dst: /usr/lib/{{ .ProjectName }}/parser
+      - src: scanner
+        dst: /usr/lib/{{ .ProjectName }}/scanner
+      - src: token
+        dst: /usr/lib/{{ .ProjectName }}/token
+      - src: watcher
+        dst: /usr/lib/{{ .ProjectName }}/watcher
       - src: "x"
-        dst: "/usr/lib/{{ .ProjectName }}/x"
+        dst: /usr/lib/{{ .ProjectName }}/x
       # symlinks to binaries
       - src: "/usr/lib/{{ .ProjectName }}/bin/gop"
         dst: /usr/bin/gop
@@ -236,66 +211,40 @@ snapcrafts:
       {{- if .Arm }}v{{ .Arm }}{{ end }}
     extra_files:
       # source folder
-      - source: "LICENSE"
-        destination: "LICENSE"
-      - source: "Makefile"
-        destination: "Makefile"
+      - source: LICENSE
+        destination: LICENSE
       - source: "README.md"
-        destination: "README.md"
-      - source: "_gendeps.go"
-        destination: "_gendeps.go"
-      - source: "all.bash"
-        destination: "all.bash"
-      - source: "all.bat"
-        destination: "all.bat"
-      - source: "ast"
-        destination: "ast"
-      - source: "build_install_run.go"
-        destination: "build_install_run.go"
-      - source: "builtin"
-        destination: "builtin"
-      - source: "cl"
-        destination: "cl"
-      - source: "cmd"
-        destination: "cmd"
-      - source: "doc"
-        destination: "doc"
-      - source: "env"
-        destination: "env"
-      - source: "format"
-        destination: "format"
-      - source: "gengo.go"
-        destination: "gengo.go"
+        destination: README.md
       - source: "go.mod"
-        destination: "go.mod"
+        destination: go.mod
       - source: "go.sum"
-        destination: "go.sum"
-      - source: "imp.go"
-        destination: "imp.go"
-      - source: "load.go"
-        destination: "load.go"
-      - source: "make.bash"
-        destination: "make.bash"
-      - source: "make.bat"
-        destination: "make.bat"
-      - source: "outline.go"
-        destination: "outline.go"
-      - source: "parser"
-        destination: "parser"
-      - source: "printer"
-        destination: "printer"
-      - source: "scanner"
-        destination: "scanner"
-      - source: "test"
-        destination: "test"
-      - source: "testdata"
-        destination: "testdata"
-      - source: "tidy.go"
-        destination: "tidy.go"
-      - source: "token"
-        destination: "token"
+        destination: go.sum
+      - source: ast
+        destination: ast
+      - source: builtin
+        destination: builtin
+      - source: cl
+        destination: cl
+      - source: cmd
+        destination: cmd
+      - source: doc
+        destination: doc
+      - source: env
+        destination: env
+      - source: format
+        destination: format
+      - source: parser
+        destination: parser
+      - source: printer
+        destination: parser
+      - source: scanner
+        destination: scanner
+      - source: token
+        destination: token
+      - source: watcher
+        destination: watcher
       - source: "x"
-        destination: "x"
+        destination: x
     apps:
       gop:
         command: "gop"
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 17ec40ec..251716a4 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -85,6 +85,36 @@ func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) {
 
 // -----------------------------------------------------------------------------
 
+// A EnvExpr node represents a ${name} expression.
+type EnvExpr struct {
+	TokPos token.Pos // position of "$"
+	Lbrace token.Pos // position of "{"
+	Name   *Ident    // name
+	Rbrace token.Pos // position of "}"
+}
+
+// Pos - position of first character belonging to the node.
+func (p *EnvExpr) Pos() token.Pos {
+	return p.TokPos
+}
+
+// End - position of first character immediately after the node.
+func (p *EnvExpr) End() token.Pos {
+	if p.Rbrace != token.NoPos {
+		return p.Rbrace
+	}
+	return p.Name.End()
+}
+
+// HasBrace checks is this EnvExpr ${name} or $name.
+func (p *EnvExpr) HasBrace() bool {
+	return p.Rbrace != token.NoPos
+}
+
+func (*EnvExpr) exprNode() {}
+
+// -----------------------------------------------------------------------------
+
 // A SliceLit node represents a slice literal.
 type SliceLit struct {
 	Lbrack     token.Pos // position of "["
diff --git a/parser/parser.go b/parser/parser.go
index c1246138..75b92bdf 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1266,6 +1266,7 @@ func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldLi
 	if p.trace {
 		defer un(trace(p, "Parameters"))
 	}
+	_ = ellipsisOk
 
 	var params []*ast.Field
 	lparen := p.expect(token.LPAREN)
@@ -1774,6 +1775,9 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 			p.resolve(x)
 		}
 		return
+
+	case token.ENV:
+		return p.parseEnvExpr(), false
 	}
 
 	typ, result := p.tryIdentOrType(stateArrayTypeOrSliceLit, nil)
@@ -1794,6 +1798,23 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 	return &ast.BadExpr{From: pos, To: p.pos}, false
 }
 
+func (p *parser) parseEnvExpr() (ret *ast.EnvExpr) {
+	if p.trace {
+		defer un(trace(p, "EnvExpr"))
+	}
+	ret = &ast.EnvExpr{TokPos: p.pos}
+	p.next()
+	if p.tok == token.LBRACE { // ${name}
+		ret.Lbrace = p.pos
+		p.next()
+		ret.Name = p.parseIdent()
+		ret.Rbrace = p.expect(token.RBRACE)
+	} else { // $name
+		ret.Name = p.parseIdent()
+	}
+	return
+}
+
 func (p *parser) parseSelector(x ast.Expr) ast.Expr {
 	if p.trace {
 		defer un(trace(p, "Selector"))
@@ -2158,6 +2179,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 	case *tupleExpr:
 		p.error(v.opening, msgTupleNotSupported)
 		x = &ast.BadExpr{From: v.opening, To: v.closing}
+	case *ast.EnvExpr:
 	default:
 		// all other nodes are not proper expressions
 		p.errorExpected(x.Pos(), "expression", 3)
@@ -2285,7 +2307,7 @@ L:
 			x = &ast.ErrWrapExpr{X: x, Tok: p.tok, TokPos: p.pos}
 			p.next()
 		default:
-			if allowCmd && p.isCmd(x) && p.checkCmd(x) {
+			if allowCmd && p.isCmd(x) && p.checkCmd() {
 				if lhs {
 					p.resolve(x)
 				}
@@ -2307,11 +2329,11 @@ func (p *parser) isCmd(x ast.Expr) bool {
 	return false
 }
 
-func (p *parser) checkCmd(x ast.Expr) bool {
+func (p *parser) checkCmd() bool {
 	switch p.tok {
 	case token.IDENT, token.DRARROW,
 		token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT,
-		token.FUNC, token.GOTO, token.MAP, token.INTERFACE, token.CHAN, token.STRUCT:
+		token.FUNC, token.GOTO, token.MAP, token.INTERFACE, token.CHAN, token.STRUCT, token.ENV:
 		return true
 	case token.SUB, token.AND, token.MUL, token.ARROW, token.XOR, token.ADD:
 		oldtok, oldpos := p.tok, p.pos
@@ -3321,7 +3343,7 @@ func (p *parser) parseStmt(allowCmd bool) (s ast.Stmt) {
 	case
 		// tokens that may start an expression
 		token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING, token.FUNC, token.LPAREN, // operands
-		token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT, // unary operators
+		token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT, token.ENV, // unary operators
 		token.LBRACK, token.STRUCT, token.CHAN, token.INTERFACE: // composite types
 		allowCmd = false
 		fallthrough
diff --git a/printer/nodes.go b/printer/nodes.go
index f6943369..c1628863 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -1101,6 +1101,14 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 			p.expr(x.Expr3)
 		}
 
+	case *ast.EnvExpr:
+		p.print(token.ENV)
+		if x.HasBrace() {
+			p.print(token.LBRACE, x.Name, token.RBRACE)
+		} else {
+			p.print(x.Name)
+		}
+
 	default:
 		log.Fatalf("unreachable %T\n", x)
 	}
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 926e337a..f84fa245 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -1002,6 +1002,8 @@ scanAgain:
 		case '?':
 			tok = token.QUESTION
 			insertSemi = true
+		case '$':
+			tok = token.ENV
 		default:
 			// next reports unexpected BOMs - don't repeat
 			if ch != bom {
diff --git a/token/token.go b/token/token.go
index 1bd7f2a9..f13a2f22 100644
--- a/token/token.go
+++ b/token/token.go
@@ -141,6 +141,9 @@ const (
 	additional_beg
 	TILDE // additional tokens, handled in an ad-hoc manner
 	additional_end
+	additional_end2
+
+	ENV = additional_end2 // ${name}
 
 	CSTRING  = literal_beg  // C"Hello"
 	RAT      = literal_end  // 123.5r
@@ -228,6 +231,7 @@ var tokens = [...]string{
 	DRARROW:   "=>",
 	SRARROW:   "->",
 	BIDIARROW: "<>",
+	ENV:       "$",
 	TILDE:     "~",
 
 	BREAK:    "break",
@@ -335,7 +339,7 @@ func (tok Token) IsLiteral() bool {
 // IsOperator returns true for tokens corresponding to operators and
 // delimiters; it returns false otherwise.
 func (tok Token) IsOperator() bool {
-	return operator_beg <= tok && tok <= operator_end || tok >= additional_beg && tok <= additional_end
+	return operator_beg <= tok && tok <= operator_end || tok >= additional_beg && tok <= additional_end2
 }
 
 // IsKeyword returns true for tokens corresponding to keywords;


Test patch:
diff --git a/parser/_testdata/envop1/envop.gop b/parser/_testdata/envop1/envop.gop
new file mode 100644
index 00000000..842986b5
--- /dev/null
+++ b/parser/_testdata/envop1/envop.gop
@@ -0,0 +1,1 @@
+echo ${name}, $id
diff --git a/parser/_testdata/envop1/parser.expect b/parser/_testdata/envop1/parser.expect
new file mode 100644
index 00000000..cc99fef5
--- /dev/null
+++ b/parser/_testdata/envop1/parser.expect
@@ -0,0 +1,30 @@
+package main
+
+file envop.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: echo
+              Args:
+                ast.EnvExpr:
+                  Name:
+                    ast.Ident:
+                      Name: name
+                ast.EnvExpr:
+                  Name:
+                    ast.Ident:
+                      Name: id
diff --git a/parser/_testdata/envop2/envop.gop b/parser/_testdata/envop2/envop.gop
new file mode 100644
index 00000000..3429d651
--- /dev/null
+++ b/parser/_testdata/envop2/envop.gop
@@ -0,0 +1,2 @@
+${name}
+echo {"id": $id}
diff --git a/parser/_testdata/envop2/parser.expect b/parser/_testdata/envop2/parser.expect
new file mode 100644
index 00000000..86833c59
--- /dev/null
+++ b/parser/_testdata/envop2/parser.expect
@@ -0,0 +1,40 @@
+package main
+
+file envop.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.EnvExpr:
+              Name:
+                ast.Ident:
+                  Name: name
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: echo
+              Args:
+                ast.CompositeLit:
+                  Elts:
+                    ast.KeyValueExpr:
+                      Key:
+                        ast.BasicLit:
+                          Kind: STRING
+                          Value: "id"
+                      Value:
+                        ast.EnvExpr:
+                          Name:
+                            ast.Ident:
+                              Name: id