Repository: goplus/gop
Pull request #2144 (2025-03-01 11:03:42)

#2143 gop/parser: ast.DomainTextLit


Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 5496a164..e3ee3c9e 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -356,9 +356,6 @@ func (x *Ident) Pos() token.Pos { return x.NamePos }
 // Pos returns position of first character belonging to the node.
 func (x *Ellipsis) Pos() token.Pos { return x.Ellipsis }
 
-// Pos returns position of first character belonging to the node.
-func (x *BasicLit) Pos() token.Pos { return x.ValuePos }
-
 // Pos returns position of first character belonging to the node.
 func (x *FuncLit) Pos() token.Pos { return x.Type.Pos() }
 
@@ -440,9 +437,6 @@ func (x *Ellipsis) End() token.Pos {
 	return x.Ellipsis + 3 // len("...")
 }
 
-// End returns position of first character immediately after the node.
-func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) }
-
 // End returns position of first character immediately after the node.
 func (x *FuncLit) End() token.Pos { return x.Body.End() }
 
@@ -520,7 +514,6 @@ func (x *ChanType) End() token.Pos { return x.Value.End() }
 func (*BadExpr) exprNode()        {}
 func (*Ident) exprNode()          {}
 func (*Ellipsis) exprNode()       {}
-func (*BasicLit) exprNode()       {}
 func (*FuncLit) exprNode()        {}
 func (*CompositeLit) exprNode()   {}
 func (*ParenExpr) exprNode()      {}
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 64fe4a85..f464b5a1 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -59,6 +59,26 @@ func (*OverloadFuncDecl) declNode() {}
 
 // -----------------------------------------------------------------------------
 
+// A DomainTextLit node represents a domain text literal.
+//
+//	tpl`...`
+type DomainTextLit struct {
+	Domain   *Ident       // domain name
+	ValuePos token.Pos    // literal position
+	Value    string       // literal string; e.g. `\m\n\o`
+	Extra    *StringLitEx // optional
+}
+
+// Pos returns position of first character belonging to the node.
+func (x *DomainTextLit) Pos() token.Pos { return x.Domain.NamePos }
+
+// End returns position of first character immediately after the node.
+func (x *DomainTextLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) }
+
+func (*DomainTextLit) exprNode() {}
+
+// -----------------------------------------------------------------------------
+
 // A BasicLit node represents a literal of basic type.
 type BasicLit struct {
 	ValuePos token.Pos    // literal position
@@ -83,6 +103,14 @@ func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) {
 	panic("NextPartPos: unexpected parameters")
 }
 
+// Pos returns position of first character belonging to the node.
+func (x *BasicLit) Pos() token.Pos { return x.ValuePos }
+
+// End returns position of first character immediately after the node.
+func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) }
+
+func (*BasicLit) exprNode() {}
+
 // -----------------------------------------------------------------------------
 
 // A NumberUnitLit node represents a number with unit.
diff --git a/ast/walk.go b/ast/walk.go
index 88a21fd5..38cfc3fc 100644
--- a/ast/walk.go
+++ b/ast/walk.go
@@ -98,7 +98,7 @@ func Walk(v Visitor, node Node) {
 		}
 
 	// Expressions
-	case *BadExpr, *Ident:
+	case *BadExpr, *Ident, *NumberUnitLit:
 		// nothing to do
 
 	case *BasicLit:
@@ -110,6 +110,16 @@ func Walk(v Visitor, node Node) {
 			}
 		}
 
+	case *DomainTextLit:
+		Walk(v, n.Domain)
+		if n.Extra != nil { // Go+ extended
+			for _, part := range n.Extra.Parts {
+				if e, ok := part.(Expr); ok {
+					Walk(v, e)
+				}
+			}
+		}
+
 	case *Ellipsis:
 		if n.Elt != nil {
 			Walk(v, n.Elt)
diff --git a/demo/gop-parser/gopcalc-2/calc.gop b/demo/gop-parser/gopcalc-2/calc.gop
index e6f44f84..384cbf56 100644
--- a/demo/gop-parser/gopcalc-2/calc.gop
+++ b/demo/gop-parser/gopcalc-2/calc.gop
@@ -18,30 +18,25 @@ func calc(e any) float64 {
 	case *tpl.Token:
 		return e.Lit.float!
 	case []any:
-		if len(e) == 2 {
-			if x, ok := e[0].(*tpl.Token); ok && x.Tok == '-' {
-				return -calc(e[1])
-			}
-			x := calc(e[0])
-			for _, r := range e[1].([]any) {
-				t := r.([]any)
-				op := t[0].(*tpl.Token)
-				y := calc(t[1])
-				switch op.Tok {
-				case '+':
-					x += y
-				case '-':
-					x -= y
-				case '*':
-					x *= y
-				case '/':
-					x /= y
-				default:
-					panic("unknown operator")
-				}
+		if x, ok := e[0].(*tpl.Token); ok && x.Tok == '-' {
+			return -calc(e[1])
+		}
+		x := calc(e[0])
+		for r <- e[1].([]any) {
+			e := r.([]any)
+			y := calc(e[1])
+			switch e[0].(*tpl.Token).Tok {
+			case '+':
+				x += y
+			case '-':
+				x -= y
+			case '*':
+				x *= y
+			case '/':
+				x /= y
 			}
-			return x
 		}
+		return x
 	}
 	panic("unknown expression")
 }
diff --git a/parser/parser.go b/parser/parser.go
index ee6b317c..7c03fb01 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1718,9 +1718,23 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 
 	switch p.tok {
 	case token.IDENT:
-		x = p.parseIdent()
-		if !lhs {
-			p.resolve(x)
+		ident := p.parseIdent()
+		if p.tok == token.STRING && p.pos == ident.End() && strings.HasPrefix(p.lit, "`") {
+			// domain text: tpl`...`
+			x = &ast.DomainTextLit{
+				Domain:   ident,
+				ValuePos: p.pos,
+				Value:    p.lit,
+			}
+			if debugParseOutput {
+				log.Printf("ast.DomainTextLit{Domain: %s, Value: %s}\n", ident.Name, p.lit)
+			}
+			p.next()
+		} else {
+			x = ident
+			if !lhs {
+				p.resolve(x)
+			}
 		}
 		return
 
@@ -2182,7 +2196,6 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 	case *ast.BadExpr:
 	case *ast.Ident:
 	case *ast.BasicLit:
-	case *ast.NumberUnitLit:
 	case *ast.FuncLit:
 	case *ast.CompositeLit:
 	case *ast.SliceLit:
@@ -2216,6 +2229,8 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 		x = &ast.BadExpr{From: v.opening, To: v.closing}
 	case *ast.EnvExpr:
 	case *ast.ElemEllipsis:
+	case *ast.NumberUnitLit:
+	case *ast.DomainTextLit:
 	default:
 		// all other nodes are not proper expressions
 		p.errorExpected(x.Pos(), "expression", 3)
diff --git a/printer/nodes.go b/printer/nodes.go
index 9899f5ca..2f36c021 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -1135,6 +1135,10 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 		p.expr(x.Elt)
 		p.print(token.ELLIPSIS)
 
+	case *ast.DomainTextLit:
+		p.print(x.Domain)
+		p.print(&ast.BasicLit{Kind: token.STRING, Value: x.Value})
+
 	default:
 		log.Fatalf("unreachable %T\n", x)
 	}


Test patch:
diff --git a/parser/_testdata/domaintext/parser.expect b/parser/_testdata/domaintext/parser.expect
new file mode 100644
index 00000000..aadb4b7f
--- /dev/null
+++ b/parser/_testdata/domaintext/parser.expect
@@ -0,0 +1,37 @@
+package main
+
+file tpl.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: cl
+          Tok: :=
+          Rhs:
+            ast.ErrWrapExpr:
+              X:
+                ast.DomainTextLit:
+                  Domain:
+                    ast.Ident:
+                      Name: tpl
+                  Value: `
+expr = termExpr % ("+" | "-")
+
+termExpr = unaryExpr % ("*" | "/")
+
+unaryExpr = operand | "-" unaryExpr
+
+operand = INT | FLOAT | "(" expr ")"
+`
+              Tok: !
diff --git a/parser/_testdata/domaintext/tpl.gop b/parser/_testdata/domaintext/tpl.gop
new file mode 100644
index 00000000..cb852f19
--- /dev/null
+++ b/parser/_testdata/domaintext/tpl.gop
@@ -0,0 +1,9 @@
+cl := tpl`
+expr = termExpr % ("+" | "-")
+
+termExpr = unaryExpr % ("*" | "/")
+
+unaryExpr = operand | "-" unaryExpr
+
+operand = INT | FLOAT | "(" expr ")"
+`!