Repository: goplus/gop
Pull request #1911 (2024-07-05 00:28:35)

PYSTRING: py"..."


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index 3a5b76d5..37798899 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -359,6 +359,7 @@ type blockCtx struct {
 	lookups    []gogen.PkgRef
 	tlookup    *typeParamLookup
 	cstr_      gogen.Ref
+	pystr_     gogen.Ref
 	relBaseDir string
 
 	classRecv *ast.FieldList // available when isClass
@@ -379,6 +380,13 @@ func (p *blockCtx) cstr() gogen.Ref {
 	return p.cstr_
 }
 
+func (p *blockCtx) pystr() gogen.Ref {
+	if p.pystr_ == nil {
+		p.pystr_ = p.pkg.Import(pathLibpy).Ref("Str")
+	}
+	return p.pystr_
+}
+
 func (p *blockCtx) recorder() *goxRecorder {
 	if p.isGopFile {
 		return p.rec
diff --git a/cl/expr.go b/cl/expr.go
index af441bc2..1337ad31 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -970,17 +970,24 @@ func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) {
 
 func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
 	cb := ctx.cb
-	switch v.Kind {
+	switch kind := v.Kind; kind {
 	case token.RAT:
 		val := v.Value
 		bi, _ := new(big.Int).SetString(val[:len(val)-1], 10) // remove r suffix
 		cb.UntypedBigInt(bi, v)
-	case token.CSTRING:
+	case token.CSTRING, token.PYSTRING:
 		s, err := strconv.Unquote(v.Value)
 		if err != nil {
 			log.Panicln("compileBasicLit:", err)
 		}
-		cb.Val(ctx.cstr()).Val(s).Call(1)
+		var xstr gogen.Ref
+		switch kind {
+		case token.CSTRING:
+			xstr = ctx.cstr()
+		default:
+			xstr = ctx.pystr()
+		}
+		cb.Val(xstr).Val(s).Call(1)
 	default:
 		if v.Extra == nil {
 			basicLit(cb, v)
diff --git a/go.mod b/go.mod
index 71f1f5e9..8a3eda46 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.18
 require (
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/goplus/gogen v1.16.0
-	github.com/goplus/llgo v0.8.10
+	github.com/goplus/llgo v0.8.11-0.20240704111710-b0941faf88bc
 	github.com/goplus/mod v0.13.10
 	github.com/qiniu/x v1.13.10
 	golang.org/x/tools v0.22.0
diff --git a/go.sum b/go.sum
index 6ad471f2..e450b4df 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/goplus/gogen v1.16.0 h1:hAK2ZX8vCjH+Y2QoJl9viSZ8Gw9pzE0vCz5voYBYnv4=
 github.com/goplus/gogen v1.16.0/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
-github.com/goplus/llgo v0.8.10 h1:JbgOpidN6q/1y8wGy2t4g5TsKCW/GJdivnAcCwmdOj8=
-github.com/goplus/llgo v0.8.10/go.mod h1:iboncA0EqWVi0MTJI1UARRE/b+EvAGblKujuIYBFj2Q=
+github.com/goplus/llgo v0.8.11-0.20240704111710-b0941faf88bc h1:GnnBpxLReBymJIgWkNqaSJWx5voztOJuPgVZYEER0d0=
+github.com/goplus/llgo v0.8.11-0.20240704111710-b0941faf88bc/go.mod h1:iboncA0EqWVi0MTJI1UARRE/b+EvAGblKujuIYBFj2Q=
 github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE=
 github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c=
 github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE=
diff --git a/parser/parser.go b/parser/parser.go
index 040b3b51..9b8ea67f 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1724,7 +1724,7 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 		}
 		return
 
-	case token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
+	case token.STRING, token.CSTRING, token.PYSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
 		bl := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
 		if p.tok == token.STRING && len(p.lit) > 1 {
 			bl.Extra = p.stringLit(p.pos, p.lit)
@@ -2353,7 +2353,8 @@ func (p *parser) isCmd(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.STRING, token.CSTRING, token.PYSTRING,
+		token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT,
 		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:
@@ -3388,7 +3389,7 @@ func (p *parser) parseStmt(allowCmd bool) (s ast.Stmt) {
 		s = &ast.DeclStmt{Decl: p.parseGenDecl(p.tok, p.parseValueSpec)}
 	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.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING, token.PYSTRING, token.FUNC, token.LPAREN, // operands
 		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
diff --git a/printer/printer.go b/printer/printer.go
index 311c5f9d..fbc3a266 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -312,8 +312,11 @@ func (p *printer) writeString(pos token.Position, s string, isLit bool) {
 		// tabwriter.Escape bytes since they do not appear in legal
 		// UTF-8 sequences.
 		p.output = append(p.output, tabwriter.Escape)
-		if p.lastTok == token.CSTRING {
+		switch p.lastTok {
+		case token.CSTRING:
 			p.output = append(p.output, 'c')
+		case token.PYSTRING:
+			p.output = append(p.output, 'p', 'y')
 		}
 	}
 
diff --git a/scanner/scanner.go b/scanner/scanner.go
index dbe0a845..3c791483 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -835,10 +835,17 @@ scanAgain:
 			// keywords are longer than one letter - avoid lookup otherwise
 			tok = token.Lookup(lit)
 			switch tok {
-			case token.IDENT, token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN:
+			case token.IDENT:
+				insertSemi = true
+				if lit == "py" && s.ch == '"' { // py"..."
+					s.next()
+					tok = token.PYSTRING
+					lit = s.scanString()
+				}
+			case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN:
 				insertSemi = true
 			}
-		} else if (lit == "C" || lit == "c") && s.ch == '"' { // C"..."
+		} else if (lit == "c" || lit == "C") && s.ch == '"' { // c"..."
 			s.next()
 			insertSemi = true
 			tok = token.CSTRING
diff --git a/token/token.go b/token/token.go
index 358d3bdd..56e3a958 100644
--- a/token/token.go
+++ b/token/token.go
@@ -143,8 +143,13 @@ const (
 	additional_end
 	additional_end2
 
+	additional_literal_beg = 96
+	additional_literal_end = 97
+
 	ENV = additional_end2 // ${name}
 
+	PYSTRING = additional_literal_beg // py"Hello"
+
 	CSTRING  = literal_beg  // c"Hello"
 	RAT      = literal_end  // 123.5r
 	DRARROW  = operator_beg // => (double right arrow)
@@ -163,14 +168,15 @@ var tokens = [...]string{
 	EOF:     "EOF",
 	COMMENT: "COMMENT",
 
-	IDENT:   "IDENT",
-	INT:     "INT",
-	FLOAT:   "FLOAT",
-	IMAG:    "IMAG",
-	CHAR:    "CHAR",
-	STRING:  "STRING",
-	CSTRING: "CSTRING",
-	RAT:     "RAT",
+	IDENT:    "IDENT",
+	INT:      "INT",
+	FLOAT:    "FLOAT",
+	IMAG:     "IMAG",
+	CHAR:     "CHAR",
+	STRING:   "STRING",
+	CSTRING:  "CSTRING",
+	PYSTRING: "PYSTRING",
+	RAT:      "RAT",
 
 	ADD: "+",
 	SUB: "-",
@@ -333,7 +339,7 @@ func Lookup(ident string) Token {
 // IsLiteral returns true for tokens corresponding to identifiers
 // and basic type literals; it returns false otherwise.
 func (tok Token) IsLiteral() bool {
-	return literal_beg <= tok && tok <= literal_end
+	return literal_beg <= tok && tok <= literal_end || tok == PYSTRING
 }
 
 // IsOperator returns true for tokens corresponding to operators and


Test patch:
diff --git a/cl/_testc/hello/in.gop b/cl/_testc/hello/in.gop
index 493d3941..4e7251d4 100644
--- a/cl/_testc/hello/in.gop
+++ b/cl/_testc/hello/in.gop
@@ -1,3 +1,3 @@
 import "c"
 
-c.Printf C"Hello, world!\n"
+c.Printf c"Hello, world!\n"
diff --git a/cl/_testpy/hello/in.gop b/cl/_testpy/hello/in.gop
new file mode 100644
index 00000000..2669fa1e
--- /dev/null
+++ b/cl/_testpy/hello/in.gop
@@ -0,0 +1,5 @@
+import (
+	"py/std"
+)
+
+std.print py"Hello, World!"
diff --git a/cl/_testpy/hello/out.go b/cl/_testpy/hello/out.go
new file mode 100644
index 00000000..fcd6a1ff
--- /dev/null
+++ b/cl/_testpy/hello/out.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+	"github.com/goplus/llgo/py"
+	"github.com/goplus/llgo/py/std"
+)
+
+func main() {
+	std.Print(py.Str("Hello, World!"))
+}
diff --git a/cl/_testpy/pycall/in.gop b/cl/_testpy/pycall/in.gop
index 20f40471..b7759703 100644
--- a/cl/_testpy/pycall/in.gop
+++ b/cl/_testpy/pycall/in.gop
@@ -5,4 +5,4 @@ import (
 )
 
 x := math.sqrt(py.float(2))
-c.printf C"sqrt(2) = %f\n", x.float64
+c.printf c"sqrt(2) = %f\n", x.float64
diff --git a/parser/_testdata/pystr/parser.expect b/parser/_testdata/pystr/parser.expect
new file mode 100644
index 00000000..4bfa5329
--- /dev/null
+++ b/parser/_testdata/pystr/parser.expect
@@ -0,0 +1,25 @@
+package main
+
+file pystr.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: print
+              Args:
+                ast.BasicLit:
+                  Kind: PYSTRING
+                  Value: "Hello"
diff --git a/parser/_testdata/pystr/pystr.gop b/parser/_testdata/pystr/pystr.gop
new file mode 100644
index 00000000..a9356c78
--- /dev/null
+++ b/parser/_testdata/pystr/pystr.gop
@@ -0,0 +1,1 @@
+print py"Hello"