Repository: goplus/xgo

Issue:
XGo domain-specific text literal
### Proposal

In markdown:

<pre>```domainTag
...
...
```</pre>

In XGo:

```
domainTag`
...
...
`
```

### Background

See https://github.com/goplus/gop/issues/1770

### Workarounds

none

Discussion:
@xushiwei: examples:

* tpl

```go
cl := tpl`expr = INT % ("+" | "-")`!
echo cl.parseExpr("1+2-3", nil)
```

* json/xml/csv

```go
echo json`{"a":1, "b":2}`
echo xml`<a>1</a>`
echo csv`a,b`
```

* html

```go
import "golang.org/x/net/html"

echo html`<html><body><h1>hello</h1></body></html>`
```

* regexp, regexposix (POSIX)

```go
re := regexp`^[a-z]+\[[0-9]+\]$`!
echo re.matchString("adam[23]")
```
@xushiwei: https://github.com/goplus/gop/blob/main/doc/domian-text-lit.md@xushiwei: domainTextLit with args:

* https://github.com/goplus/gop/pull/2302
* https://github.com/goplus/gop/pull/2303

```go
// A DomainTextLit node represents a domain-specific text literal.
// https://github.com/goplus/gop/issues/2143
//
//	domainTag`...`
//	domainTag`> arg1, arg2, ...
//	  ...
//	`
```@xushiwei: @niupilot Please add documentation related to domain-specific text literal to the quick start.@niupilot: ### Documentation Added Successfully! ✅

I've added comprehensive documentation for domain-specific text literals to the quick start guide.

### What was added:

- **New section**: "Domain-Specific Text Literals" in the quick start guide
- **Syntax examples**: Both basic and parameterized domain text literal syntax
- **Built-in formats**: Documented tpl, json, xml, csv, html, and regexp with code examples
- **How it works**: Explained the function call mechanism
- **Extensibility**: Information on creating custom domain text formats
- **Table of contents**: Updated to include the new section

### Pull Request

Created PR #2460 with all the changes: https://github.com/goplus/xgo/pull/2460

The documentation is now ready for review and provides clear, practical examples for users to understand and use domain-specific text literals in XGo.

---
[Job Run](https://github.com/goplus/xgo/actions) | Branch: `codeagent/claude/issue-2143-1760151236` | [PR #2460](https://github.com/goplus/xgo/pull/2460)

Solution patch:
diff --git a/.github/codecov.yml b/.github/codecov.yml
index c87961da..3d97bb0c 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -11,6 +11,7 @@ coverage:
     - "cmd"
     - "cl/cltest"
     - "cl/outline"
+    - "cl/internal/huh"
     - "cl/internal/llgo-hello"
     - "cl/internal/mcp"
     - "cl/internal/overload"
diff --git a/cl/_testgop/domaintext-huh/in.gop b/cl/_testgop/domaintext-huh/in.gop
new file mode 100644
index 00000000..ac5fc26a
--- /dev/null
+++ b/cl/_testgop/domaintext-huh/in.gop
@@ -0,0 +1,8 @@
+import "github.com/goplus/gop/cl/internal/huh"
+
+form := huh`> "1", 2
+<form id="test">
+</form>
+`
+
+form.run
diff --git a/cl/expr.go b/cl/expr.go
index 6b351729..ffc1ec55 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -1082,8 +1082,13 @@ const (
 	tplPkgPath = "github.com/goplus/gop/tpl"
 )
 
+// A DomainTextLit node represents a domain-specific text literal.
 // https://github.com/goplus/gop/issues/2143
-// domainTag`...` => domainTag.new(`...`)
+//
+//	domainTag`...`
+//	domainTag`> arg1, arg2, ...
+//	  ...
+//	`
 func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) {
 	var cb = ctx.cb
 	var imp gogen.PkgRef
@@ -1137,8 +1142,16 @@ func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) {
 			}
 		}
 	} else {
-		cb.Val(imp.Ref("New")).
-			Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v)
+		cb.Val(imp.Ref("New"))
+		if lit, ok := v.Extra.(*ast.DomainTextLitEx); ok {
+			cb.Val(lit.Raw)
+			for _, arg := range lit.Args {
+				compileExpr(ctx, arg)
+			}
+			n += len(lit.Args)
+		} else {
+			cb.Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v)
+		}
 	}
 	cb.CallWith(n, 0, v)
 }
diff --git a/cl/internal/huh/huh.go b/cl/internal/huh/huh.go
new file mode 100644
index 00000000..ef550746
--- /dev/null
+++ b/cl/internal/huh/huh.go
@@ -0,0 +1,14 @@
+package huh
+
+// -----------------------------------------------------------------------------
+
+type Form int
+
+func (f Form) Run() {
+}
+
+func New(string, string, int) Form {
+	return 0
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/internal/mcp/classfile.go b/cl/internal/mcp/classfile.go
index a5eb8571..1bc3a2d7 100644
--- a/cl/internal/mcp/classfile.go
+++ b/cl/internal/mcp/classfile.go
@@ -1,19 +1,3 @@
-/*
- * 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.
- */
-
 package mcp
 
 const (
diff --git a/cl/internal/unit/unit.go b/cl/internal/unit/unit.go
index 6325b8d3..8b79feaa 100644
--- a/cl/internal/unit/unit.go
+++ b/cl/internal/unit/unit.go
@@ -1,16 +1,3 @@
-/*
- Copyright 2024 The GoPlus Authors (goplus.org)
- 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.
-*/
-
 package unit
 
 // -----------------------------------------------------------------------------
diff --git a/parser/parser.go b/parser/parser.go
index 2cf617fd..918c00f9 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1785,7 +1785,7 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 			var extra any
 			if ident.Name == "tpl" {
 				extra = p.tplLit(pos+1, pos+token.Pos(len(lit))-1)
-			} else if strings.HasPrefix(lit, "`< ") { // domainTag`< ...`
+			} else if strings.HasPrefix(lit, "`> ") { // domainTag`> ...`
 				extra = p.domainTextLitEx(pos+3, pos+token.Pos(len(lit))-1)
 			}
 			x = &ast.DomainTextLit{



Test patch:
diff --git a/cl/_testgop/domaintext-huh/out.go b/cl/_testgop/domaintext-huh/out.go
new file mode 100644
index 00000000..579fd798
--- /dev/null
+++ b/cl/_testgop/domaintext-huh/out.go
@@ -0,0 +1,8 @@
+package main
+
+import "github.com/goplus/gop/cl/internal/huh"
+
+func main() {
+	form := huh.New("<form id=\"test\">\n</form>\n", "1", 2)
+	form.Run()
+}
diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go
index d8bc5a0c..256d2b1b 100644
--- a/cl/cltest/cltest.go
+++ b/cl/cltest/cltest.go
@@ -43,6 +43,7 @@ import (
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/gopmod"
 	"github.com/goplus/mod/modfile"
+	"github.com/qiniu/x/test"
 )
 
 var (
@@ -141,7 +142,7 @@ func Mixed(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...b
 
 func DoFS(
 	t *testing.T, conf *cl.Config,
-	fs parser.FileSystem, dir string, filter func(fs.FileInfo) bool, pkgname, expected string) {
+	fs parser.FileSystem, dir string, filter func(fs.FileInfo) bool, pkgname string, exp any) {
 	cl.SetDisableRecover(true)
 	defer cl.SetDisableRecover(false)
 
@@ -164,9 +165,13 @@ func DoFS(
 	if err != nil {
 		t.Fatal("gogen.WriteTo failed:", err)
 	}
-	result := b.String()
-	if result != expected {
-		t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
+	if expected, ok := exp.(string); ok {
+		result := b.String()
+		if result != expected {
+			t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
+		}
+	} else if test.Diff(t, dir+"/result.txt", b.Bytes(), exp.([]byte)) {
+		t.Fatal(dir, ": unexpect result")
 	}
 }
 
@@ -199,11 +204,7 @@ func testFrom(t *testing.T, pkgDir, sel string) {
 	}
 	log.Println("Parsing", pkgDir)
 	out := pkgDir + "/out.go"
-	b, err := os.ReadFile(out)
-	if err != nil {
-		t.Fatal("ReadFile failed:", err)
-	}
-	expected := string(b)
+	b, _ := os.ReadFile(out)
 	filter := func(fi fs.FileInfo) bool {
 		return fi.Name() == "in.gop"
 	}
@@ -220,7 +221,7 @@ func testFrom(t *testing.T, pkgDir, sel string) {
 		confCopy.RelativeBase = GopRoot
 		conf = &confCopy
 	}
-	DoFS(t, conf, fsx.Local, pkgDir, filter, "main", expected)
+	DoFS(t, conf, fsx.Local, pkgDir, filter, "main", b)
 }
 
 func Go1Point() int {
diff --git a/parser/_testdata/domainhuh/huh.gop b/parser/_testdata/domainhuh/huh.gop
index 2bfcdb77..d470f579 100644
--- a/parser/_testdata/domainhuh/huh.gop
+++ b/parser/_testdata/domainhuh/huh.gop
@@ -1,4 +1,4 @@
-form := huh`< &ret, 10
+form := huh`> &ret, 10
 <form>
 	<group>
 		<select id="Burger" title="Choose your burger">
diff --git a/parser/_testdata/domainhuh/parser.expect b/parser/_testdata/domainhuh/parser.expect
index 9323c3cf..ffb01ccf 100644
--- a/parser/_testdata/domainhuh/parser.expect
+++ b/parser/_testdata/domainhuh/parser.expect
@@ -23,7 +23,7 @@ ast.FuncDecl:
               Domain:
                 ast.Ident:
                   Name: huh
-              Value: `< &ret, 10
+              Value: `> &ret, 10
 <form>
 	<group>
 		<select id="Burger" title="Choose your burger">