Repository: goplus/gop
Pull request #2340 (2025-05-07 09:09:05)

v1.4.5


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/.github/workflows/go.yml b/.github/workflows/go.yml
index 006efac8..032acfe5 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -88,3 +88,22 @@ jobs:
       uses: codecov/codecov-action@v5
       with:
         slug: goplus/gop
+
+  FormatCheck:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: "1.24.2"
+
+      - name: Check formatting
+        run: |
+          if [ -n "$(go fmt ./... | grep -v gop_autogen)" ]; then
+            echo "Some files are not properly formatted. Please run 'go fmt ./...'"
+            exit 1
+          fi
+          ./all.bash
+          cd cmd/xgo; gop fmt -t ./...
diff --git a/.gitignore b/.gitignore
index 053202fd..06d35e99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ coverage.txt
 
 .gop/
 gop_autogen*.go
+!cmd/xgo/gop_autogen*.go
 go.json
 go.work*
 format.result
diff --git a/README.md b/README.md
index a0fb13fa..4be5516d 100644
--- a/README.md
+++ b/README.md
@@ -165,7 +165,7 @@ One language can change the whole world.
 Go+ is a "DSL" for all domains.
 ```
 
-Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` is as important to Go+ as `interface` is to Go.
+Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` (and `class framework`) is as important to Go+ as `interface` is to Go.
 
 In the design philosophy of Go+, we do not recommend `DSL` (Domain Specific Language). But `SDF` (Specific Domain Friendliness) is very important. The Go+ philosophy about `SDF` is:
 
@@ -174,12 +174,12 @@ Don't define a language for specific domain.
 Abstract domain knowledge for it.
 ```
 
-Go+ introduces `classfile` to abstract domain knowledge.
+Go+ introduces `classfile` and `class framework` to abstract domain knowledge.
 
 * [What's Classfile?](doc/classfile.md#whats-classfile)
 * [Dive into Go+ Classfiles](doc/classfile.md)
 
-Sound a bit abstract? Let's see some Go+ classfiles.
+Sound a bit abstract? Let's see some Go+ class frameworks.
 
 * STEM Education: [spx: A Go+ 2D Game Engine](https://github.com/goplus/spx)
 * AI Programming: [mcp: A Go+ implementation of the Model Context Protocol (MCP)](https://github.com/goplus/mcp)
@@ -189,7 +189,7 @@ Sound a bit abstract? Let's see some Go+ classfiles.
 * Web Programming: [ydb: A Go+ Database Framework](https://github.com/goplus/yap/tree/main/ydb)
 * CLI Programming: [cobra: A Commander for modern Go+ CLI interactions](https://github.com/goplus/cobra)
 * CLI Programming: [gsh: An alternative to write shell scripts](https://github.com/qiniu/x/tree/main/gsh)
-* Unit Test: [test: Unit Test](https://github.com/goplus/gop/blob/main/doc/classfile.md#classfile-unit-test)
+* Unit Test: [test: Unit Test](doc/classfile.md#class-framework-unit-test)
 
 
 ### yap: Yet Another HTTP Web Framework
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 0d0c52f8..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.4.0
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 8703c101..afa29c47 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -62,12 +62,22 @@ func (*OverloadFuncDecl) declNode() {}
 // A DomainTextLit node represents a domain-specific text literal.
 // https://github.com/goplus/gop/issues/2143
 //
-//	tpl`...`
+//	domainTag`...`
+//	domainTag`> arg1, arg2, ...
+//	  ...
+//	`
 type DomainTextLit struct {
 	Domain   *Ident    // domain name
 	ValuePos token.Pos // literal position
 	Value    string    // literal string; e.g. `\m\n\o`
-	Extra    any       // *ast.StringLitEx or *gop/tpl/ast.File, optional
+	Extra    any       // *DomainTextLitEx or *gop/tpl/ast.File, optional
+}
+
+// DomainTextLitEx represents extra information for domain text literal.
+type DomainTextLitEx struct {
+	Args   []Expr    // domain text arguments; or nil
+	RawPos token.Pos // position of the first character of the raw string
+	Raw    string    // raw string without backquote
 }
 
 // Pos returns position of first character belonging to the node.
diff --git a/builtin/doc/builtin.gop b/builtin/doc.gop
old mode 100755
new mode 100644
similarity index 83%
rename from builtin/doc/builtin.gop
rename to builtin/doc.gop
index 373db69b..816aba34
--- a/builtin/doc/builtin.gop
+++ b/builtin/doc.gop
@@ -1,7 +1,7 @@
 package builtin
 
 import (
-	"gop/builtin/iox"
+	"github.com/qiniu/x/gop/osx"
 	"io"
 	"os"
 	"reflect"
@@ -11,7 +11,9 @@ import (
 // Spaces are always added between operands and a newline is appended.
 // It returns the number of bytes written and any write error encountered.
 func Echo(a ...any) (n int, err error)
-func Blines(r io.Reader) iox.BLineReader
+
+// Blines returns a BLineReader that reads lines from the given reader.
+func Blines(r io.Reader) osx.BLineReader
 
 // Type returns the reflection [Type] that represents the dynamic type of i.
 // If i is a nil interface value, Type returns nil.
@@ -75,12 +77,23 @@ func Sprintf(format string, a ...any) string
 func Open(name string) (*os.File, error)
 
 // Create creates or truncates the named file. If the file already exists,
-// it is truncated. If the file does not exist, it is created with mode 0666
+// it is truncated. If the file does not exist, it is created with mode 0o666
 // (before umask). If successful, methods on the returned File can
 // be used for I/O; the associated file descriptor has mode O_RDWR.
+// The directory containing the file must already exist.
 // If there is an error, it will be of type *PathError.
 func Create(name string) (*os.File, error)
-func Lines(r io.Reader) iox.LineReader
+
+// Lines returns a LineReader that reads lines from the given reader.
+func Lines(r io.Reader) osx.LineReader
+
+// Errorln formats and prints to standard error.
+func Errorln(args ...any)
+
+// Fatal formats and prints to standard error.
+func Fatal(args ...any)
+
+// Gop_Enum returns a LineIter that reads lines from the given reader.
 func (r io.Reader) Gop_Enum() iox.LineIter
 
 // String is equivalent to strconv.FormatFloat(f, 'g', -1, 64)
@@ -91,14 +104,14 @@ func (r io.Reader) Gop_Enum() iox.LineIter
 // value of bitSize bits (32 for float32, 64 for float64).
 //
 // The format fmt is one of
-// 'b' (-ddddp±ddd, a binary exponent),
-// 'e' (-d.dddde±dd, a decimal exponent),
-// 'E' (-d.ddddE±dd, a decimal exponent),
-// 'f' (-ddd.dddd, no exponent),
-// 'g' ('e' for large exponents, 'f' otherwise),
-// 'G' ('E' for large exponents, 'f' otherwise),
-// 'x' (-0xd.ddddp±ddd, a hexadecimal fraction and binary exponent), or
-// 'X' (-0Xd.ddddP±ddd, a hexadecimal fraction and binary exponent).
+//   - 'b' (-ddddp±ddd, a binary exponent),
+//   - 'e' (-d.dddde±dd, a decimal exponent),
+//   - 'E' (-d.ddddE±dd, a decimal exponent),
+//   - 'f' (-ddd.dddd, no exponent),
+//   - 'g' ('e' for large exponents, 'f' otherwise),
+//   - 'G' ('E' for large exponents, 'f' otherwise),
+//   - 'x' (-0xd.ddddp±ddd, a hexadecimal fraction and binary exponent), or
+//   - 'X' (-0Xd.ddddP±ddd, a hexadecimal fraction and binary exponent).
 //
 // The precision prec controls the number of digits (excluding the exponent)
 // printed by the 'e', 'E', 'f', 'g', 'G', 'x', and 'X' formats.
@@ -107,9 +120,11 @@ func (r io.Reader) Gop_Enum() iox.LineIter
 // zeros are removed).
 // The special precision -1 uses the smallest number of digits
 // necessary such that ParseFloat will return f exactly.
+// The exponent is written as a decimal integer;
+// for all formats other than 'b', it will be at least two digits.
 func (f float64) String() string
 
-// String is equivalent to FormatInt(int64(i), 10).
+// String is equivalent to [FormatInt](int64(i), 10).
 func (i int) String() string
 
 // String is equivalent to strconv.FormatInt(i, 10)
@@ -128,12 +143,12 @@ func (u uint64) String() string
 
 // The len built-in function returns the length of v, according to its type:
 //
-//	Array: the number of elements in v.
-//	Pointer to array: the number of elements in *v (even if v is nil).
-//	Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
-//	String: the number of bytes in v.
-//	Channel: the number of elements queued (unread) in the channel buffer;
-//	         if v is nil, len(v) is zero.
+//   - Array: the number of elements in v.
+//   - Pointer to array: the number of elements in *v (even if v is nil).
+//   - Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
+//   - String: the number of bytes in v.
+//   - Channel: the number of elements queued (unread) in the channel buffer;
+//     if v is nil, len(v) is zero.
 //
 // For some arguments, such as a string literal or a simple array expression, the
 // result can be a constant. See the Go language specification's "Length and
@@ -165,11 +180,11 @@ func (s string) Int() (int, error)
 // correspond to int, int8, int16, int32, and int64.
 // If bitSize is below 0 or above 64, an error is returned.
 //
-// The errors that ParseInt returns have concrete type *NumError
+// The errors that ParseInt returns have concrete type [*NumError]
 // and include err.Num = s. If s is empty or contains invalid
-// digits, err.Err = ErrSyntax and the returned value is 0;
+// digits, err.Err = [ErrSyntax] and the returned value is 0;
 // if the value corresponding to s cannot be represented by a
-// signed integer of the given size, err.Err = ErrRange and the
+// signed integer of the given size, err.Err = [ErrRange] and the
 // returned value is the maximum magnitude integer of the
 // appropriate bitSize and sign.
 //
@@ -178,7 +193,7 @@ func (s string) Int64() (i int64, err error)
 
 // Uint64 is equivalent to strconv.ParseUint(s, 10, 64)
 //
-// ParseUint is like ParseInt but for unsigned numbers.
+// ParseUint is like [ParseInt] but for unsigned numbers.
 //
 // A sign prefix is not permitted.
 func (s string) Uint64() (uint64, error)
@@ -226,7 +241,7 @@ func (s string) IndexByte(c byte) int
 
 // IndexRune returns the index of the first instance of the Unicode code point
 // r, or -1 if rune is not present in s.
-// If r is utf8.RuneError, it returns the first instance of any
+// If r is [utf8.RuneError], it returns the first instance of any
 // invalid UTF-8 byte sequence.
 func (s string) IndexRune(r rune) int
 
@@ -253,9 +268,9 @@ func (s string) ContainsRune(r rune) bool
 // Compare returns an integer comparing two strings lexicographically.
 // The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
 //
-// Compare is included only for symmetry with package bytes.
-// It is usually clearer and always faster to use the built-in
-// string comparison operators ==, <, >, and so on.
+// Use Compare when you need to perform a three-way comparison (with
+// [slices.SortFunc], for example). It is usually clearer and always faster
+// to use the built-in string comparison operators ==, <, >, and so on.
 func (s string) Compare(b string) int
 
 // EqualFold reports whether s and t, interpreted as UTF-8 strings,
@@ -272,14 +287,15 @@ func (s string) HasSuffix(suffix string) bool
 // Quote returns a double-quoted Go string literal representing s. The
 // returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for
 // control characters and non-printable characters as defined by
-// IsPrint.
+// [IsPrint].
 func (s string) Quote() string
 
 // Unquote interprets s as a single-quoted, double-quoted,
 // or backquoted Go string literal, returning the string value
 // that s quotes.  (If s is single-quoted, it would be a Go
 // character literal; Unquote returns the corresponding
-// one-character string.)
+// one-character string. For an empty character literal
+// Unquote returns the empty string.)
 func (s string) Unquote() (string, error)
 
 // ToTitle returns a copy of the string s with all Unicode letters mapped to
@@ -293,7 +309,7 @@ func (s string) ToUpper() string
 func (s string) ToLower() string
 
 // Fields splits the string s around each instance of one or more consecutive white space
-// characters, as defined by unicode.IsSpace, returning a slice of substrings of s or an
+// characters, as defined by [unicode.IsSpace], returning a slice of substrings of s or an
 // empty slice if s contains only white space.
 func (s string) Fields() []string
 
@@ -314,7 +330,7 @@ func (s string) Repeat(count int) string
 //
 // It is equivalent to [SplitN] with a count of -1.
 //
-// To split around the first instance of a separator, see Cut.
+// To split around the first instance of a separator, see [Cut].
 func (s string) Split(sep string) []string
 
 // SplitAfter slices s into all substrings after each instance of sep and
@@ -333,37 +349,30 @@ func (s string) SplitAfter(sep string) []string
 // the substrings between those separators.
 //
 // The count determines the number of substrings to return:
-//
-//	n > 0: at most n substrings; the last substring will be the unsplit remainder.
-//	n == 0: the result is nil (zero substrings)
-//	n < 0: all substrings
+//   - n > 0: at most n substrings; the last substring will be the unsplit remainder;
+//   - n == 0: the result is nil (zero substrings);
+//   - n < 0: all substrings.
 //
 // Edge cases for s and sep (for example, empty strings) are handled
 // as described in the documentation for [Split].
 //
-// To split around the first instance of a separator, see Cut.
+// To split around the first instance of a separator, see [Cut].
 func (s string) SplitN(sep string, n int) []string
 
 // SplitAfterN slices s into substrings after each instance of sep and
 // returns a slice of those substrings.
 //
 // The count determines the number of substrings to return:
-//
-//	n > 0: at most n substrings; the last substring will be the unsplit remainder.
-//	n == 0: the result is nil (zero substrings)
-//	n < 0: all substrings
+//   - n > 0: at most n substrings; the last substring will be the unsplit remainder;
+//   - n == 0: the result is nil (zero substrings);
+//   - n < 0: all substrings.
 //
 // Edge cases for s and sep (for example, empty strings) are handled
-// as described in the documentation for SplitAfter.
+// as described in the documentation for [SplitAfter].
 func (s string) SplitAfterN(sep string, n int) []string
 
-// Replace returns a copy of the string s with the first n
-// non-overlapping instances of old replaced by new.
-// If old is empty, it matches at the beginning of the string
-// and after each UTF-8 sequence, yielding up to k+1 replacements
-// for a k-rune string.
-// If n < 0, there is no limit on the number of replacements.
-func (s string) Replace(old, new string, n int) string
+// Replace returns a copy of s with all replacements performed.
+func (s string) Replace() string
 
 // ReplaceAll returns a copy of the string s with all
 // non-overlapping instances of old replaced by new.
@@ -406,12 +415,12 @@ func (s string) Capitalize() string
 
 // The len built-in function returns the length of v, according to its type:
 //
-//	Array: the number of elements in v.
-//	Pointer to array: the number of elements in *v (even if v is nil).
-//	Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
-//	String: the number of bytes in v.
-//	Channel: the number of elements queued (unread) in the channel buffer;
-//	         if v is nil, len(v) is zero.
+//   - Array: the number of elements in v.
+//   - Pointer to array: the number of elements in *v (even if v is nil).
+//   - Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
+//   - String: the number of bytes in v.
+//   - Channel: the number of elements queued (unread) in the channel buffer;
+//     if v is nil, len(v) is zero.
 //
 // For some arguments, such as a string literal or a simple array expression, the
 // result can be a constant. See the Go language specification's "Length and
@@ -420,12 +429,12 @@ func (v []string) Len() int
 
 // The cap built-in function returns the capacity of v, according to its type:
 //
-//	Array: the number of elements in v (same as len(v)).
-//	Pointer to array: the number of elements in *v (same as len(v)).
-//	Slice: the maximum length the slice can reach when resliced;
-//	if v is nil, cap(v) is zero.
-//	Channel: the channel buffer capacity, in units of elements;
-//	if v is nil, cap(v) is zero.
+//   - Array: the number of elements in v (same as len(v)).
+//   - Pointer to array: the number of elements in *v (same as len(v)).
+//   - Slice: the maximum length the slice can reach when resliced;
+//     if v is nil, cap(v) is zero.
+//   - Channel: the channel buffer capacity, in units of elements;
+//     if v is nil, cap(v) is zero.
 //
 // For some arguments, such as a simple array expression, the result can be a
 // constant. See the Go language specification's "Length and capacity" section for
diff --git a/builtin/iox/enum.go b/builtin/iox/enum.go
deleted file mode 100644
index eec0c269..00000000
--- a/builtin/iox/enum.go
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- Copyright 2022 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 iox
-
-import (
-	"bufio"
-	"io"
-)
-
-// ----------------------------------------------------------------------------
-
-type LineIter struct {
-	s *bufio.Scanner
-}
-
-func (it LineIter) Next() (line string, ok bool) {
-	s := it.s
-	if ok = s.Scan(); ok {
-		line = s.Text()
-	} else if err := s.Err(); err != nil {
-		panic(err)
-	}
-	return
-}
-
-func EnumLines(r io.Reader) LineIter {
-	scanner := bufio.NewScanner(r)
-	return LineIter{scanner}
-}
-
-// ----------------------------------------------------------------------------
-
-type BLineIter struct {
-	s *bufio.Scanner
-}
-
-func (it BLineIter) Next() (line []byte, ok bool) {
-	s := it.s
-	if ok = s.Scan(); ok {
-		line = s.Bytes()
-	} else if err := s.Err(); err != nil {
-		panic(err)
-	}
-	return
-}
-
-func EnumBLines(r io.Reader) BLineIter {
-	scanner := bufio.NewScanner(r)
-	return BLineIter{scanner}
-}
-
-// ----------------------------------------------------------------------------
-
-type LineReader struct {
-	r io.Reader
-}
-
-func (p LineReader) Gop_Enum() LineIter {
-	return EnumLines(p.r)
-}
-
-func Lines(r io.Reader) LineReader {
-	return LineReader{r}
-}
-
-// ----------------------------------------------------------------------------
-
-type BLineReader struct {
-	r io.Reader
-}
-
-func (p BLineReader) Gop_Enum() BLineIter {
-	return EnumBLines(p.r)
-}
-
-func BLines(r io.Reader) BLineReader {
-	return BLineReader{r}
-}
-
-// ----------------------------------------------------------------------------
diff --git a/builtin/ng/_big.gop b/builtin/ng/_big.gop
deleted file mode 100644
index b1b00d91..00000000
--- a/builtin/ng/_big.gop
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- Copyright 2021 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.
-*/
-
-// +build ignore
-
-package _
-
-import (
-	"math/big"
-)
-
-// -----------------------------------------------------------------------------
-// type bigint
-
-// A bigint represents a signed multi-precision integer.
-// The zero value for a bigint represents nil.
-type bigint struct {
-	*big.Int
-}
-
-func (a bigint) + (b bigint) bigint
-func (a bigint) - (b bigint) bigint
-func (a bigint) * (b bigint) bigint
-func (a bigint) / (b bigint) bigint
-func (a bigint) % (b bigint) bigint
-
-func (a bigint) | (b bigint) bigint
-func (a bigint) ^ (b bigint) bigint
-func (a bigint) & (b bigint) bigint
-func (a bigint) &^ (b bigint) bigint
-
-func (a bigint) << (n untyped_uint) bigint
-func (a bigint) >> (n untyped_uint) bigint
-func (a bigint) < (b bigint) bool
-func (a bigint) <= (b bigint) bool
-func (a bigint) > (b bigint) bool
-func (a bigint) >= (b bigint) bool
-func (a bigint) == (b bigint) bool
-func (a bigint) != (b bigint) bool
-
-func -(a bigint) bigint
-func ^(a bigint) bigint
-
-func ++(a bigint)
-func --(a bigint)
-
-func (a bigint) = (b bigint)
-func (a bigint) += (b bigint)
-func (a bigint) -= (b bigint)
-func (a bigint) *= (b bigint)
-func (a bigint) /= (b bigint)
-func (a bigint) %= (b bigint)
-
-func (a bigint) |= (b bigint)
-func (a bigint) ^= (b bigint)
-func (a bigint) &= (b bigint)
-func (a bigint) &^= (b bigint)
-
-func (a bigint) <<= (n untyped_uint)
-func (a bigint) >>= (n untyped_uint)
-
-func bigint() bigint
-func bigint(x int64) bigint
-
-// -----------------------------------------------------------------------------
-// type bigrat
-
-// A bigrat represents a quotient a/b of arbitrary precision.
-// The zero value for a bigrat represents nil.
-type bigrat struct {
-	*big.Rat
-}
-
-func (a bigrat) + (b bigrat) bigrat
-func (a bigrat) - (b bigrat) bigrat
-func (a bigrat) * (b bigrat) bigrat
-func (a bigrat) / (b bigrat) bigrat
-
-func (a bigrat) < (b bigrat) bool
-func (a bigrat) <= (b bigrat) bool
-func (a bigrat) > (b bigrat) bool
-func (a bigrat) >= (b bigrat) bool
-func (a bigrat) == (b bigrat) bool
-func (a bigrat) != (b bigrat) bool
-
-func -(a bigrat) bigrat
-func /(a bigrat) bigrat
-
-func (a bigrat) = (b bigrat)
-func (a bigrat) += (b bigrat)
-func (a bigrat) -= (b bigrat)
-func (a bigrat) *= (b bigrat)
-func (a bigrat) /= (b bigrat)
-
-func bigrat() bigrat
-func bigrat(a bigint) bigrat
-func bigrat(a, b int64) bigrat
-
-// -----------------------------------------------------------------------------
-// type bigfloat
-
-// A bigfloat represents a multi-precision floating point number.
-// The zero value for a bigfloat represents nil.
-type bigfloat struct {
-	*big.Float
-}
-
-// -----------------------------------------------------------------------------
diff --git a/builtin/ng/big.go b/builtin/ng/big.go
deleted file mode 100644
index 00ebeee9..00000000
--- a/builtin/ng/big.go
+++ /dev/null
@@ -1,514 +0,0 @@
-/*
- * Copyright (c) 2021 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 ng
-
-import (
-	"math/big"
-)
-
-func Gop_istmp(a any) bool {
-	return false
-}
-
-// -----------------------------------------------------------------------------
-
-type UntypedBigint *big.Int
-type UntypedBigrat *big.Rat
-type UntypedBigfloat *big.Float
-
-type UntypedBigint_Default = Bigint
-type UntypedBigrat_Default = Bigrat
-type UntypedBigfloat_Default = Bigfloat
-
-func UntypedBigint_Init__0(x int) UntypedBigint {
-	panic(makeCompilerHappy)
-}
-
-func UntypedBigrat_Init__0(x int) UntypedBigrat {
-	panic(makeCompilerHappy)
-}
-
-func UntypedBigrat_Init__1(x UntypedBigint) UntypedBigrat {
-	panic(makeCompilerHappy)
-}
-
-const (
-	makeCompilerHappy = "make compiler happy"
-)
-
-// -----------------------------------------------------------------------------
-// type bigint
-
-// A Bigint represents a signed multi-precision integer.
-// The zero value for a Bigint represents nil.
-type Bigint struct {
-	*big.Int
-}
-
-func tmpint(a, b Bigint) Bigint {
-	if Gop_istmp(a) {
-		return a
-	} else if Gop_istmp(b) {
-		return b
-	}
-	return Bigint{new(big.Int)}
-}
-
-func tmpint1(a Bigint) Bigint {
-	if Gop_istmp(a) {
-		return a
-	}
-	return Bigint{new(big.Int)}
-}
-
-// IsNil returns a bigint object is nil or not
-func (a Bigint) IsNil() bool {
-	return a.Int == nil
-}
-
-// Gop_Add: func (a bigint) + (b bigint) bigint
-func (a Bigint) Gop_Add(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Add(a.Int, b.Int)}
-}
-
-// Gop_Sub: func (a bigint) - (b bigint) bigint
-func (a Bigint) Gop_Sub(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Sub(a.Int, b.Int)}
-}
-
-// Gop_Mul: func (a bigint) * (b bigint) bigint
-func (a Bigint) Gop_Mul(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Mul(a.Int, b.Int)}
-}
-
-// Gop_Quo: func (a bigint) / (b bigint) bigint {
-func (a Bigint) Gop_Quo(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Quo(a.Int, b.Int)}
-}
-
-// Gop_Rem: func (a bigint) % (b bigint) bigint
-func (a Bigint) Gop_Rem(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Rem(a.Int, b.Int)}
-}
-
-// Gop_Or: func (a bigint) | (b bigint) bigint
-func (a Bigint) Gop_Or(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Or(a.Int, b.Int)}
-}
-
-// Gop_Xor: func (a bigint) ^ (b bigint) bigint
-func (a Bigint) Gop_Xor(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).Xor(a.Int, b.Int)}
-}
-
-// Gop_And: func (a bigint) & (b bigint) bigint
-func (a Bigint) Gop_And(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).And(a.Int, b.Int)}
-}
-
-// Gop_AndNot: func (a bigint) &^ (b bigint) bigint
-func (a Bigint) Gop_AndNot(b Bigint) Bigint {
-	return Bigint{tmpint(a, b).AndNot(a.Int, b.Int)}
-}
-
-// Gop_Lsh: func (a bigint) << (n untyped_uint) bigint
-func (a Bigint) Gop_Lsh(n Gop_ninteger) Bigint {
-	return Bigint{tmpint1(a).Lsh(a.Int, uint(n))}
-}
-
-// Gop_Rsh: func (a bigint) >> (n untyped_uint) bigint
-func (a Bigint) Gop_Rsh(n Gop_ninteger) Bigint {
-	return Bigint{tmpint1(a).Rsh(a.Int, uint(n))}
-}
-
-// Gop_LT: func (a bigint) < (b bigint) bool
-func (a Bigint) Gop_LT(b Bigint) bool {
-	return a.Cmp(b.Int) < 0
-}
-
-// Gop_LE: func (a bigint) <= (b bigint) bool
-func (a Bigint) Gop_LE(b Bigint) bool {
-	return a.Cmp(b.Int) <= 0
-}
-
-// Gop_GT: func (a bigint) > (b bigint) bool
-func (a Bigint) Gop_GT(b Bigint) bool {
-	return a.Cmp(b.Int) > 0
-}
-
-// Gop_GE: func (a bigint) >= (b bigint) bool
-func (a Bigint) Gop_GE(b Bigint) bool {
-	return a.Cmp(b.Int) >= 0
-}
-
-// Gop_EQ: func (a bigint) == (b bigint) bool
-func (a Bigint) Gop_EQ(b Bigint) bool {
-	return a.Cmp(b.Int) == 0
-}
-
-// Gop_NE: func (a bigint) != (b bigint) bool
-func (a Bigint) Gop_NE(b Bigint) bool {
-	return a.Cmp(b.Int) != 0
-}
-
-// Gop_Neg: func -(a bigint) bigint
-func (a Bigint) Gop_Neg() Bigint {
-	return Bigint{tmpint1(a).Neg(a.Int)}
-}
-
-// Gop_Dup: func +(a bigint) bigint
-func (a Bigint) Gop_Dup() Bigint {
-	return Bigint{new(big.Int).Set(a.Int)}
-}
-
-// Gop_Not: func ^(a bigint) bigint
-func (a Bigint) Gop_Not() Bigint {
-	return Bigint{tmpint1(a).Not(a.Int)}
-}
-
-// Gop_Inc: func ++(b bigint)
-func (a Bigint) Gop_Inc() {
-	a.Int.Add(a.Int, big1)
-}
-
-// Gop_Dec: func --(b bigint)
-func (a Bigint) Gop_Dec() {
-	a.Int.Sub(a.Int, big1)
-}
-
-// Gop_AddAssign: func (a bigint) += (b bigint)
-func (a Bigint) Gop_AddAssign(b Bigint) {
-	a.Int.Add(a.Int, b.Int)
-}
-
-// Gop_SubAssign: func (a bigint) -= (b bigint)
-func (a Bigint) Gop_SubAssign(b Bigint) {
-	a.Int.Sub(a.Int, b.Int)
-}
-
-// Gop_MulAssign: func (a bigint) *= (b bigint)
-func (a Bigint) Gop_MulAssign(b Bigint) {
-	a.Int.Mul(a.Int, b.Int)
-}
-
-// Gop_QuoAssign: func (a bigint) /= (b bigint) {
-func (a Bigint) Gop_QuoAssign(b Bigint) {
-	a.Int.Quo(a.Int, b.Int)
-}
-
-// Gop_RemAssign: func (a bigint) %= (b bigint)
-func (a Bigint) Gop_RemAssign(b Bigint) {
-	a.Int.Rem(a.Int, b.Int)
-}
-
-// Gop_OrAssign: func (a bigint) |= (b bigint)
-func (a Bigint) Gop_OrAssign(b Bigint) {
-	a.Int.Or(a.Int, b.Int)
-}
-
-// Gop_XorAssign: func (a bigint) ^= (b bigint)
-func (a Bigint) Gop_XorAssign(b Bigint) {
-	a.Int.Xor(a.Int, b.Int)
-}
-
-// Gop_AndAssign: func (a bigint) &= (b bigint)
-func (a Bigint) Gop_AndAssign(b Bigint) {
-	a.Int.And(a.Int, b.Int)
-}
-
-// Gop_AndNotAssign: func (a bigint) &^= (b bigint)
-func (a Bigint) Gop_AndNotAssign(b Bigint) {
-	a.Int.AndNot(a.Int, b.Int)
-}
-
-// Gop_LshAssign: func (a bigint) <<= (n untyped_uint)
-func (a Bigint) Gop_LshAssign(n Gop_ninteger) {
-	a.Int.Lsh(a.Int, uint(n))
-}
-
-// Gop_RshAssign: func (a bigint) >>= (n untyped_uint)
-func (a Bigint) Gop_RshAssign(n Gop_ninteger) {
-	a.Int.Rsh(a.Int, uint(n))
-}
-
-// -----------------------------------------------------------------------------
-
-// Gop_Rcast: func int64(x bigint) int64
-func (a Bigint) Gop_Rcast__0() int64 {
-	return a.Int64()
-}
-
-// Gop_Rcast: func int64(x bigint) (int64, bool)
-func (a Bigint) Gop_Rcast__1() (int64, bool) {
-	return a.Int64(), a.IsInt64()
-}
-
-// Gop_Rcast: func uint64(x bigint) uint64
-func (a Bigint) Gop_Rcast__2() uint64 {
-	return a.Uint64()
-}
-
-// Gop_Rcast: func uint64(x bigint) (uint64, bool)
-func (a Bigint) Gop_Rcast__3() (uint64, bool) {
-	return a.Uint64(), a.IsUint64()
-}
-
-// Bigint_Cast: func bigint(x untyped_int) bigint
-func Bigint_Cast__0(x int) Bigint {
-	return Bigint{big.NewInt(int64(x))}
-}
-
-// Bigint_Cast: func bigint(x untyped_bigint) bigint
-func Bigint_Cast__1(x UntypedBigint) Bigint {
-	return Bigint{x}
-}
-
-// Bigint_Cast: func bigint(x int64) bigint
-func Bigint_Cast__2(x int64) Bigint {
-	return Bigint{big.NewInt(x)}
-}
-
-// Bigint_Cast: func bigint(x uint64) bigint
-func Bigint_Cast__3(x uint64) Bigint {
-	return Bigint{new(big.Int).SetUint64(x)}
-}
-
-// Bigint_Cast: func bigint(x uint) bigint
-func Bigint_Cast__4(x uint) Bigint {
-	return Bigint{new(big.Int).SetUint64(uint64(x))}
-}
-
-// Bigint_Cast: func bigint(x *big.Int) bigint
-func Bigint_Cast__5(x *big.Int) Bigint {
-	return Bigint{x}
-}
-
-// Bigint_Cast: func bigint(x *big.Rat) bigint
-func Bigint_Cast__6(x *big.Rat) Bigint {
-	if x.IsInt() {
-		return Bigint{x.Num()}
-	}
-	ret, _ := new(big.Float).SetRat(x).Int(nil)
-	return Bigint{ret}
-}
-
-// Bigint_Cast: func bigint() bigint
-func Bigint_Cast__7() Bigint {
-	return Bigint{new(big.Int)}
-}
-
-// Bigint_Init: func bigint.init(x int) bigint
-func Bigint_Init__0(x int) Bigint {
-	return Bigint{big.NewInt(int64(x))}
-}
-
-// Bigint_Init: func bigint.init(x untyped_bigint) bigint
-func Bigint_Init__1(x UntypedBigint) Bigint {
-	return Bigint{x}
-}
-
-// Bigint_Init: func bigint.init(x *big.Int) bigint
-func Bigint_Init__2(x *big.Int) Bigint {
-	return Bigint{x}
-}
-
-// -----------------------------------------------------------------------------
-// type bigrat
-
-// A Bigrat represents a quotient a/b of arbitrary precision.
-// The zero value for a Bigrat represents nil.
-type Bigrat struct {
-	*big.Rat
-}
-
-func tmprat(a, b Bigrat) Bigrat {
-	if Gop_istmp(a) {
-		return a
-	} else if Gop_istmp(b) {
-		return b
-	}
-	return Bigrat{new(big.Rat)}
-}
-
-func tmprat1(a Bigrat) Bigrat {
-	if Gop_istmp(a) {
-		return a
-	}
-	return Bigrat{new(big.Rat)}
-}
-
-// IsNil returns a bigrat object is nil or not
-func (a Bigrat) IsNil() bool {
-	return a.Rat == nil
-}
-
-// Gop_Assign: func (a bigrat) = (b bigrat)
-func (a Bigrat) Gop_Assign(b Bigrat) {
-	if Gop_istmp(b) {
-		*a.Rat = *b.Rat
-	} else {
-		a.Rat.Set(b.Rat)
-	}
-}
-
-// Gop_Add: func (a bigrat) + (b bigrat) bigrat
-func (a Bigrat) Gop_Add(b Bigrat) Bigrat {
-	return Bigrat{tmprat(a, b).Add(a.Rat, b.Rat)}
-}
-
-// Gop_Sub: func (a bigrat) - (b bigrat) bigrat
-func (a Bigrat) Gop_Sub(b Bigrat) Bigrat {
-	return Bigrat{tmprat(a, b).Sub(a.Rat, b.Rat)}
-}
-
-// Gop_Mul: func (a bigrat) * (b bigrat) bigrat
-func (a Bigrat) Gop_Mul(b Bigrat) Bigrat {
-	return Bigrat{tmprat(a, b).Mul(a.Rat, b.Rat)}
-}
-
-// Gop_Quo: func (a bigrat) / (b bigrat) bigrat
-func (a Bigrat) Gop_Quo(b Bigrat) Bigrat {
-	return Bigrat{tmprat(a, b).Quo(a.Rat, b.Rat)}
-}
-
-// Gop_LT: func (a bigrat) < (b bigrat) bool
-func (a Bigrat) Gop_LT(b Bigrat) bool {
-	return a.Cmp(b.Rat) < 0
-}
-
-// Gop_LE: func (a bigrat) <= (b bigrat) bool
-func (a Bigrat) Gop_LE(b Bigrat) bool {
-	return a.Cmp(b.Rat) <= 0
-}
-
-// Gop_GT: func (a bigrat) > (b bigrat) bool
-func (a Bigrat) Gop_GT(b Bigrat) bool {
-	return a.Cmp(b.Rat) > 0
-}
-
-// Gop_GE: func (a bigrat) >= (b bigrat) bool
-func (a Bigrat) Gop_GE(b Bigrat) bool {
-	return a.Cmp(b.Rat) >= 0
-}
-
-// Gop_EQ: func (a bigrat) == (b bigrat) bool
-func (a Bigrat) Gop_EQ(b Bigrat) bool {
-	return a.Cmp(b.Rat) == 0
-}
-
-// Gop_NE: func (a bigrat) != (b bigrat) bool
-func (a Bigrat) Gop_NE(b Bigrat) bool {
-	return a.Cmp(b.Rat) != 0
-}
-
-// Gop_Neg: func -(a bigrat) bigrat
-func (a Bigrat) Gop_Neg() Bigrat {
-	return Bigrat{tmprat1(a).Neg(a.Rat)}
-}
-
-// Gop_Dup: func +(a bigrat) bigrat
-func (a Bigrat) Gop_Dup() Bigrat {
-	return Bigrat{new(big.Rat).Set(a.Rat)}
-}
-
-// Gop_Inv: func /(a bigrat) bigrat
-func (a Bigrat) Gop_Inv() Bigrat {
-	return Bigrat{tmprat1(a).Inv(a.Rat)}
-}
-
-// Gop_Add: func (a bigrat) += (b bigrat)
-func (a Bigrat) Gop_AddAssign(b Bigrat) {
-	a.Rat.Add(a.Rat, b.Rat)
-}
-
-// Gop_Sub: func (a bigrat) -= (b bigrat)
-func (a Bigrat) Gop_SubAssign(b Bigrat) {
-	a.Rat.Sub(a.Rat, b.Rat)
-}
-
-// Gop_Mul: func (a bigrat) *= (b bigrat)
-func (a Bigrat) Gop_MulAssign(b Bigrat) {
-	a.Rat.Mul(a.Rat, b.Rat)
-}
-
-// Gop_Quo: func (a bigrat) /= (b bigrat)
-func (a Bigrat) Gop_QuoAssign(b Bigrat) {
-	a.Rat.Quo(a.Rat, b.Rat)
-}
-
-// -----------------------------------------------------------------------------
-
-// Bigrat_Cast: func bigrat(x untyped_int) bigrat
-func Bigrat_Cast__0(x int) Bigrat {
-	return Bigrat{big.NewRat(int64(x), 1)}
-}
-
-// Bigrat_Cast: func bigrat(a untyped_bigint) bigrat
-func Bigrat_Cast__1(a UntypedBigint) Bigrat {
-	return Bigrat{new(big.Rat).SetInt(a)}
-}
-
-// Bigrat_Cast: func bigrat(a *big.Int) bigrat
-func Bigrat_Cast__2(a *big.Int) Bigrat {
-	return Bigrat{new(big.Rat).SetInt(a)}
-}
-
-// Bigrat_Cast: func bigrat(a bigint) bigrat
-func Bigrat_Cast__3(a Bigint) Bigrat {
-	return Bigrat{new(big.Rat).SetInt(a.Int)}
-}
-
-// Bigrat_Cast: func bigrat(a *big.Rat) bigrat
-func Bigrat_Cast__4(a *big.Rat) Bigrat {
-	return Bigrat{a}
-}
-
-// Bigrat_Cast: func bigrat() bigrat
-func Bigrat_Cast__5() Bigrat {
-	return Bigrat{new(big.Rat)}
-}
-
-// Bigrat_Cast: func bigrat(a, b int64) bigrat
-func Bigrat_Cast__6(a, b int64) Bigrat {
-	return Bigrat{big.NewRat(a, b)}
-}
-
-// Bigrat_Init: func bigrat.init(x untyped_int) bigrat
-func Bigrat_Init__0(x int) Bigrat {
-	return Bigrat{big.NewRat(int64(x), 1)}
-}
-
-// Bigrat_Init: func bigrat.init(x untyped_bigint) bigrat
-func Bigrat_Init__1(x UntypedBigint) Bigrat {
-	return Bigrat{new(big.Rat).SetInt(x)}
-}
-
-// Bigrat_Init: func bigrat.init(x *big.Rat) bigrat
-func Bigrat_Init__2(x *big.Rat) Bigrat {
-	return Bigrat{x}
-}
-
-// -----------------------------------------------------------------------------
-// type bigfloat
-
-// A Bigfloat represents a multi-precision floating point number.
-// The zero value for a Float represents nil.
-type Bigfloat struct {
-	*big.Float
-}
-
-// -----------------------------------------------------------------------------
diff --git a/builtin/ng/int128.go b/builtin/ng/int128.go
deleted file mode 100644
index 707caadd..00000000
--- a/builtin/ng/int128.go
+++ /dev/null
@@ -1,819 +0,0 @@
-package ng
-
-import (
-	"fmt"
-	"log"
-	"math/big"
-	"math/bits"
-)
-
-const (
-	Int128_Max       = 1<<127 - 1
-	Int128_Min       = -1 << 127
-	Int128_IsUntyped = true
-)
-
-const (
-	signBit  = 0x8000000000000000
-	maxInt64 = 1<<63 - 1
-)
-
-var (
-	big1       = new(big.Int).SetUint64(1)
-	bigMaxU128 *big.Int
-)
-
-func init() {
-	bigMaxU128 = new(big.Int).Lsh(big1, 128)
-	bigMaxU128.Sub(bigMaxU128, big1) // 340282366920938463463374607431768211455
-}
-
-// -----------------------------------------------------------------------------
-
-type Int128 struct {
-	hi uint64
-	lo uint64
-}
-
-// Int128_Init: func int128.init(v int) int128
-func Int128_Init__0(v int) (out Int128) {
-	return Int128_Cast__2(int64(v))
-}
-
-// Int128_Init: func int128.init(v untyped_bigint) int128
-func Int128_Init__1(v UntypedBigint) (out Int128) {
-	return Int128_Cast__1(v)
-}
-
-// Int128_Cast: func int128(v int) int128
-func Int128_Cast__0(v int) Int128 {
-	return Int128_Cast__2(int64(v))
-}
-
-// Int128_Cast: func int128(v untyped_bigint) int128
-func Int128_Cast__1(v UntypedBigint) (out Int128) {
-	out, inRange := Int128_Cast__9(v)
-	if !inRange {
-		log.Panicf("value %v was not in valid int128 range\n", v)
-	}
-	return
-}
-
-// Int128_Cast: func int128(v int64) int128
-func Int128_Cast__2(v int64) (out Int128) {
-	var hi uint64
-	if v < 0 {
-		hi = maxUint64
-	}
-	return Int128{hi: hi, lo: uint64(v)}
-}
-
-// Int128_Cast: func int128(v uint64) int128
-func Int128_Cast__3(v uint64) Int128 {
-	return Int128{lo: v}
-}
-
-// Int128_Cast: func int128(v int32) int128
-func Int128_Cast__4(v int32) Int128 {
-	return Int128_Cast__2(int64(v))
-}
-
-// Int128_Cast: func int128(v int16) int128
-func Int128_Cast__5(v int16) Int128 {
-	return Int128_Cast__2(int64(v))
-}
-
-// Int128_Cast: func int128(v int8) int128
-func Int128_Cast__6(v int8) Int128 {
-	return Int128_Cast__2(int64(v))
-}
-
-// Int128_Cast: func int128(v uint18) int128
-func Int128_Cast__7(v Uint128) (out Int128) {
-	return Int128{hi: v.hi, lo: v.lo}
-}
-
-// Int128_Cast: func int128(v *big.Int) int128
-func Int128_Cast__8(v *big.Int) Int128 {
-	out, _ := Int128_Cast__9(v)
-	return out
-}
-
-func Int128_Cast__9(v *big.Int) (out Int128, inRange bool) {
-	neg := v.Sign() < 0
-	words := v.Bits()
-
-	var u Uint128
-	inRange = true
-	switch intSize {
-	case 64:
-		lw := len(words)
-		switch lw {
-		case 0:
-		case 1:
-			u.lo = uint64(words[0])
-		case 2:
-			u.hi = uint64(words[1])
-			u.lo = uint64(words[0])
-		default:
-			u, inRange = Uint128{hi: maxUint64, lo: maxUint64}, false
-		}
-
-	case 32:
-		lw := len(words)
-		switch lw {
-		case 0:
-		case 1:
-			u.lo = uint64(words[0])
-		case 2:
-			u.lo = (uint64(words[1]) << 32) | (uint64(words[0]))
-		case 3:
-			u.hi = uint64(words[2])
-			u.lo = (uint64(words[1]) << 32) | (uint64(words[0]))
-		case 4:
-			u.hi = (uint64(words[3]) << 32) | (uint64(words[2]))
-			u.lo = (uint64(words[1]) << 32) | (uint64(words[0]))
-		default:
-			u, inRange = Uint128{hi: maxUint64, lo: maxUint64}, false
-		}
-
-	default:
-		panic("unsupported bit size")
-	}
-
-	if neg {
-		if cmp := u.Cmp__1(Uint128{hi: 0x8000000000000000, lo: 0}); cmp > 0 {
-			out, inRange = Int128{hi: 0x8000000000000000, lo: 0}, false
-		} else {
-			out = Int128{hi: u.hi, lo: u.lo}.Gop_Neg()
-		}
-	} else {
-		if cmp := u.Cmp__1(Uint128{hi: maxInt64, lo: maxUint64}); cmp > 0 {
-			out, inRange = Int128{hi: maxInt64, lo: maxUint64}, false
-		} else {
-			out = Int128{hi: u.hi, lo: u.lo}
-		}
-	}
-	return
-}
-
-// Int128_Cast: func int128() int128
-func Int128_Cast__a() Int128 {
-	return Int128{}
-}
-
-// Gop_Rcast: func uint128(v int128) uint128
-func (i Int128) Gop_Rcast__0() Uint128 {
-	return Uint128{lo: i.lo, hi: i.hi}
-}
-
-// Gop_Rcast: func uint128(v int128) (uint128, bool)
-func (i Int128) Gop_Rcast__1() (out Uint128, inRange bool) {
-	return Uint128{lo: i.lo, hi: i.hi}, i.hi&signBit == 0
-}
-
-// Gop_Rcast: func int64(v int128) int64
-func (i Int128) Gop_Rcast__2() int64 {
-	if i.hi&signBit == 0 {
-		return int64(i.lo)
-	}
-	return -int64(^(i.lo - 1))
-}
-
-// Gop_Rcast: func int64(v int128) (int64, bool)
-func (i Int128) Gop_Rcast__3() (out int64, inRange bool) {
-	if i.hi&signBit == 0 {
-		return int64(i.lo), i.hi == 0 && i.lo <= maxInt64
-	}
-	return -int64(^(i.lo - 1)), i.hi == maxUint64 && i.lo >= 0x8000000000000000
-}
-
-// Gop_Rcast: func uint64(v int128) uint64
-func (i Int128) Gop_Rcast__4() uint64 {
-	return i.lo
-}
-
-// Gop_Rcast: func uint64(v int128) (uint64, bool)
-func (i Int128) Gop_Rcast__5() (out uint64, inRange bool) {
-	return i.lo, i.hi == 0
-}
-
-// -----------------------------------------------------------------------------
-
-func (i Int128) IsZero() bool {
-	return i.lo == 0 && i.hi == 0
-}
-
-func (i *Int128) Scan(state fmt.ScanState, verb rune) (err error) {
-	t, err := state.Token(true, nil)
-	if err != nil {
-		return
-	}
-	v, err := ParseInt128(string(t), 10)
-	if err == nil {
-		*i = v
-	}
-	return
-}
-
-func (i Int128) Format(s fmt.State, c rune) {
-	// TODO: not so good
-	i.BigInt().Format(s, c)
-}
-
-func (i Int128) String() string {
-	return i.Text(10)
-}
-
-func (i Int128) Text(base int) string {
-	// TODO: not so good
-	return i.BigInt().Text(base)
-}
-
-func (i Int128) BigInt() *big.Int {
-	var v big.Int
-	i.ToBigInt(&v)
-	return &v
-}
-
-func (i Int128) ToBigInt(b *big.Int) {
-	neg := i.hi&signBit != 0
-	if i.hi > 0 {
-		b.SetUint64(i.hi)
-		b.Lsh(b, 64)
-	}
-	var lo big.Int
-	lo.SetUint64(i.lo)
-	b.Add(b, &lo)
-
-	if neg {
-		b.Xor(b, bigMaxU128).Add(b, big1).Neg(b)
-	}
-}
-
-func (i Int128) Sign() int {
-	if i.lo == 0 && i.hi == 0 {
-		return 0
-	} else if i.hi&signBit == 0 {
-		return 1
-	}
-	return -1
-}
-
-func (i *Int128) Gop_Inc() {
-	i.lo++
-	if i.lo == 0 {
-		i.hi++
-	}
-}
-
-func (i *Int128) Gop_Dec() {
-	if i.lo == 0 {
-		i.hi--
-	}
-	i.lo--
-}
-
-// Gop_AddAssign: func (a *int128) += (b int128)
-func (i *Int128) Gop_AddAssign(b Int128) {
-	*i = i.Gop_Add__1(b)
-}
-
-// Gop_SubAssign: func (a *int128) -= (b int128)
-func (i *Int128) Gop_SubAssign(b Int128) {
-	*i = i.Gop_Sub__1(b)
-}
-
-// Gop_MulAssign: func (a *int128) *= (b int128)
-func (i *Int128) Gop_MulAssign(b Int128) {
-	*i = i.Gop_Mul__1(b)
-}
-
-// Gop_QuoAssign: func (a *int128) /= (b int128) {
-func (i *Int128) Gop_QuoAssign(b Int128) {
-	*i = i.Gop_Quo__1(b)
-}
-
-// Gop_RemAssign: func (a *int128) %= (b int128)
-func (i *Int128) Gop_RemAssign(b Int128) {
-	*i = i.Gop_Rem__1(b)
-}
-
-// Gop_OrAssign: func (a *int128) |= (b int128)
-func (i *Int128) Gop_OrAssign(b Int128) {
-	*i = i.Gop_Or(b)
-}
-
-// Gop_XorAssign: func (a *int128) ^= (b int128)
-func (i *Int128) Gop_XorAssign(b Int128) {
-	*i = i.Gop_Xor(b)
-}
-
-// Gop_AndAssign: func (a *int128) &= (b int128)
-func (i *Int128) Gop_AndAssign(b Int128) {
-	*i = i.Gop_And(b)
-}
-
-// Gop_AndNotAssign: func (a *int128) &^= (b int128)
-func (i *Int128) Gop_AndNotAssign(b Int128) {
-	*i = i.Gop_AndNot(b)
-}
-
-// Gop_LshAssign: func (a *int128) <<= (n untyped_uint)
-func (i *Int128) Gop_LshAssign(n Gop_ninteger) {
-	*i = i.Gop_Lsh(n)
-}
-
-// Gop_RshAssign: func (a *int128) >>= (n untyped_uint)
-func (i *Int128) Gop_RshAssign(n Gop_ninteger) {
-	*i = i.Gop_Rsh(n)
-}
-
-func (i Int128) Gop_Add__1(n Int128) (v Int128) {
-	var carry uint64
-	v.lo, carry = bits.Add64(i.lo, n.lo, 0)
-	v.hi, _ = bits.Add64(i.hi, n.hi, carry)
-	return v
-}
-
-func (i Int128) Gop_Add__0(n int64) (v Int128) {
-	var carry uint64
-	v.lo, carry = bits.Add64(i.lo, uint64(n), 0)
-	if n < 0 {
-		v.hi = i.hi + maxUint64 + carry
-	} else {
-		v.hi = i.hi + carry
-	}
-	return v
-}
-
-func (i Int128) Gop_Sub__1(n Int128) (v Int128) {
-	var borrowed uint64
-	v.lo, borrowed = bits.Sub64(i.lo, n.lo, 0)
-	v.hi, _ = bits.Sub64(i.hi, n.hi, borrowed)
-	return v
-}
-
-func (i Int128) Gop_Sub__0(n int64) (v Int128) {
-	var borrowed uint64
-	if n < 0 {
-		v.lo, borrowed = bits.Sub64(i.lo, uint64(n), 0)
-		v.hi = i.hi - maxUint64 - borrowed
-	} else {
-		v.lo, borrowed = bits.Sub64(i.lo, uint64(n), 0)
-		v.hi = i.hi - borrowed
-	}
-	return v
-}
-
-func (i Int128) Gop_Neg() (v Int128) {
-	if i.lo == 0 && i.hi == 0 {
-		return
-	}
-	if i.hi&signBit == 0 {
-		v.hi = ^i.hi
-		v.lo = (^i.lo) + 1
-	} else {
-		v.hi = ^i.hi
-		v.lo = ^(i.lo - 1)
-	}
-	if v.lo == 0 { // handle overflow
-		v.hi++
-	}
-	return v
-}
-
-func (i Int128) Gop_Dup() (v Int128) {
-	return i
-}
-
-// Abs returns the absolute value of i as a signed integer.
-func (i Int128) Abs__0() Int128 {
-	if i.hi&signBit != 0 {
-		i.hi = ^i.hi
-		i.lo = ^(i.lo - 1)
-		if i.lo == 0 { // handle carry
-			i.hi++
-		}
-	}
-	return i
-}
-
-func (i Int128) Abs__1() (ret Int128, inRange bool) {
-	return i.Abs__0(), i.hi != 0x8000000000000000 || i.lo != 0
-}
-
-// AbsU returns the absolute value of i as an unsigned integer. All
-// values of i are representable using this function, but the type is
-// changed.
-func (i Int128) AbsU() Uint128 {
-	if i.hi == 0x8000000000000000 && i.lo == 0 {
-		return Uint128{hi: 0x8000000000000000}
-	}
-	if i.hi&signBit != 0 {
-		i.hi = ^i.hi
-		i.lo = ^(i.lo - 1)
-		if i.lo == 0 { // handle carry
-			i.hi++
-		}
-	}
-	return Uint128{hi: i.hi, lo: i.lo}
-}
-
-// Cmp compares i to n and returns:
-//
-//	< 0 if i <  n
-//	  0 if i == n
-//	> 0 if i >  n
-//
-// The specific value returned by Cmp is undefined, but it is guaranteed to
-// satisfy the above constraints.
-func (i Int128) Cmp__1(n Int128) int {
-	if i.hi == n.hi && i.lo == n.lo {
-		return 0
-	} else if i.hi&signBit == n.hi&signBit {
-		if i.hi > n.hi || (i.hi == n.hi && i.lo > n.lo) {
-			return 1
-		}
-	} else if i.hi&signBit == 0 {
-		return 1
-	}
-	return -1
-}
-
-// Cmp64 compares 'i' to 64-bit int 'n' and returns:
-//
-//	< 0 if i <  n
-//	  0 if i == n
-//	> 0 if i >  n
-//
-// The specific value returned by Cmp is undefined, but it is guaranteed to
-// satisfy the above constraints.
-func (i Int128) Cmp__0(n int64) int {
-	var nhi uint64
-	var nlo = uint64(n)
-	if n < 0 {
-		nhi = maxUint64
-	}
-	if i.hi == nhi && i.lo == nlo {
-		return 0
-	} else if i.hi&signBit == nhi&signBit {
-		if i.hi > nhi || (i.hi == nhi && i.lo > nlo) {
-			return 1
-		}
-	} else if i.hi&signBit == 0 {
-		return 1
-	}
-	return -1
-}
-
-func (i Int128) Gop_EQ__1(n Int128) bool {
-	return i.hi == n.hi && i.lo == n.lo
-}
-
-func (i Int128) Gop_EQ__0(n int64) bool {
-	var nhi uint64
-	var nlo = uint64(n)
-	if n < 0 {
-		nhi = maxUint64
-	}
-	return i.hi == nhi && i.lo == nlo
-}
-
-func (i Int128) Gop_GT__1(n Int128) bool {
-	if i.hi&signBit == n.hi&signBit {
-		return i.hi > n.hi || (i.hi == n.hi && i.lo > n.lo)
-	} else if i.hi&signBit == 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_GT__0(n int64) bool {
-	var nhi uint64
-	var nlo = uint64(n)
-	if n < 0 {
-		nhi = maxUint64
-	}
-
-	if i.hi&signBit == nhi&signBit {
-		return i.hi > nhi || (i.hi == nhi && i.lo > nlo)
-	} else if i.hi&signBit == 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_GE__1(n Int128) bool {
-	if i.hi == n.hi && i.lo == n.lo {
-		return true
-	}
-	if i.hi&signBit == n.hi&signBit {
-		return i.hi > n.hi || (i.hi == n.hi && i.lo > n.lo)
-	} else if i.hi&signBit == 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_GE__0(n int64) bool {
-	var nhi uint64
-	var nlo = uint64(n)
-	if n < 0 {
-		nhi = maxUint64
-	}
-
-	if i.hi == nhi && i.lo == nlo {
-		return true
-	}
-	if i.hi&signBit == nhi&signBit {
-		return i.hi > nhi || (i.hi == nhi && i.lo > nlo)
-	} else if i.hi&signBit == 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_LT__1(n Int128) bool {
-	if i.hi&signBit == n.hi&signBit {
-		return i.hi < n.hi || (i.hi == n.hi && i.lo < n.lo)
-	} else if i.hi&signBit != 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_LT__0(n int64) bool {
-	var nhi uint64
-	var nlo = uint64(n)
-	if n < 0 {
-		nhi = maxUint64
-	}
-
-	if i.hi&signBit == nhi&signBit {
-		return i.hi < nhi || (i.hi == nhi && i.lo < nlo)
-	} else if i.hi&signBit != 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_LE__1(n Int128) bool {
-	if i.hi == n.hi && i.lo == n.lo {
-		return true
-	}
-	if i.hi&signBit == n.hi&signBit {
-		return i.hi < n.hi || (i.hi == n.hi && i.lo < n.lo)
-	} else if i.hi&signBit != 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_LE__0(n int64) bool {
-	var nhi uint64
-	var nlo = uint64(n)
-	if n < 0 {
-		nhi = maxUint64
-	}
-
-	if i.hi == nhi && i.lo == nlo {
-		return true
-	}
-	if i.hi&signBit == nhi&signBit {
-		return i.hi < nhi || (i.hi == nhi && i.lo < nlo)
-	} else if i.hi&signBit != 0 {
-		return true
-	}
-	return false
-}
-
-func (i Int128) Gop_And(n Int128) Int128 {
-	i.hi &= n.hi
-	i.lo &= n.lo
-	return i
-}
-
-func (i Int128) Gop_AndNot(n Int128) Int128 {
-	i.hi &^= n.hi
-	i.lo &^= n.lo
-	return i
-}
-
-func (i Int128) Gop_Not() Int128 {
-	return Int128{hi: ^i.hi, lo: ^i.lo}
-}
-
-func (i Int128) Gop_Or(n Int128) Int128 {
-	i.hi |= n.hi
-	i.lo |= n.lo
-	return i
-}
-
-func (i Int128) Gop_Xor(v Int128) Int128 {
-	i.hi ^= v.hi
-	i.lo ^= v.lo
-	return i
-}
-
-func (i Int128) Gop_Lsh(n Gop_ninteger) Int128 {
-	if n < 64 {
-		i.hi = (i.hi << n) | (i.lo >> (64 - n))
-		i.lo <<= n
-	} else {
-		i.hi = i.lo << (n - 64)
-		i.lo = 0
-	}
-	return i
-}
-
-func (i Int128) Gop_Rsh(n Gop_ninteger) Int128 {
-	if n < 64 {
-		i.lo = (i.lo >> n) | (i.hi << (64 - n))
-	} else {
-		i.lo = i.hi >> (n - 64)
-	}
-	i.hi = uint64(int64(i.hi) >> n)
-	return i
-}
-
-// Mul returns the product of two I128s.
-//
-// Overflow should wrap around, as per the Go spec.
-func (i Int128) Gop_Mul__1(n Int128) (dest Int128) {
-	hi, lo := bits.Mul64(i.lo, n.lo)
-	hi += i.hi*n.lo + i.lo*n.hi
-	return Int128{hi, lo}
-}
-
-func (i Int128) Gop_Mul__0(n int64) Int128 {
-	nlo := uint64(n)
-	var nhi uint64
-	if n < 0 {
-		nhi = maxUint64
-	}
-	hi, lo := bits.Mul64(i.lo, nlo)
-	hi += i.hi*nlo + i.lo*nhi
-	return Int128{hi, lo}
-}
-
-// QuoRem returns the quotient q and remainder r for y != 0. If y == 0, a
-// division-by-zero run-time panic occurs.
-//
-// QuoRem implements T-division and modulus (like Go):
-//
-//	q = x/y      with the result truncated to zero
-//	r = x - y*q
-//
-// U128 does not support big.Int.DivMod()-style Euclidean division.
-//
-// Note: dividing MinI128 by -1 will overflow, returning MinI128, as
-// per the Go spec (https://golang.org/ref/spec#Integer_operators):
-//
-//	The one exception to this rule is that if the dividend x is the most
-//	negative value for the int type of x, the quotient q = x / -1 is equal to x
-//	(and r = 0) due to two's-complement integer overflow.
-func (i Int128) QuoRem__1(by Int128) (q, r Int128) {
-	qSign, rSign := 1, 1
-	if i.Gop_LT__0(0) {
-		qSign, rSign = -1, -1
-		i = i.Gop_Neg()
-	}
-	if by.Gop_LT__0(0) {
-		qSign = -qSign
-		by = by.Gop_Neg()
-	}
-
-	qu, ru := i.Gop_Rcast__0().QuoRem__1(by.Gop_Rcast__0())
-	q, r = Int128_Cast__7(qu), Int128_Cast__7(ru)
-	if qSign < 0 {
-		q = q.Gop_Neg()
-	}
-	if rSign < 0 {
-		r = r.Gop_Neg()
-	}
-	return q, r
-}
-
-func (i Int128) QuoRem__0(by int64) (q, r Int128) {
-	ineg := i.hi&signBit != 0
-	if ineg {
-		i = i.Gop_Neg()
-	}
-	byneg := by < 0
-	if byneg {
-		by = -by
-	}
-
-	n := uint64(by)
-	if i.hi < n {
-		q.lo, r.lo = bits.Div64(i.hi, i.lo, n)
-	} else {
-		q.hi, r.lo = bits.Div64(0, i.hi, n)
-		q.lo, r.lo = bits.Div64(r.lo, i.lo, n)
-	}
-	if ineg != byneg {
-		q = q.Gop_Neg()
-	}
-	if ineg {
-		r = r.Gop_Neg()
-	}
-	return q, r
-}
-
-// Quo returns the quotient x/y for y != 0. If y == 0, a division-by-zero
-// run-time panic occurs. Quo implements truncated division (like Go); see
-// QuoRem for more details.
-func (i Int128) Gop_Quo__1(by Int128) (q Int128) {
-	qSign := 1
-	if i.Gop_LT__0(0) {
-		qSign = -1
-		i = i.Gop_Neg()
-	}
-	if by.Gop_LT__0(0) {
-		qSign = -qSign
-		by = by.Gop_Neg()
-	}
-
-	qu := i.Gop_Rcast__0().Gop_Quo__1(by.Gop_Rcast__0())
-	q = Int128_Cast__7(qu)
-	if qSign < 0 {
-		q = q.Gop_Neg()
-	}
-	return q
-}
-
-func (i Int128) Gop_Quo__0(by int64) (q Int128) {
-	ineg := i.hi&signBit != 0
-	if ineg {
-		i = i.Gop_Neg()
-	}
-	byneg := by < 0
-	if byneg {
-		by = -by
-	}
-
-	n := uint64(by)
-	if i.hi < n {
-		q.lo, _ = bits.Div64(i.hi, i.lo, n)
-	} else {
-		var rlo uint64
-		q.hi, rlo = bits.Div64(0, i.hi, n)
-		q.lo, _ = bits.Div64(rlo, i.lo, n)
-	}
-	if ineg != byneg {
-		q = q.Gop_Neg()
-	}
-	return q
-}
-
-// Gop_Rem returns the remainder of x%y for y != 0. If y == 0, a division-by-zero
-// run-time panic occurs. Gop_Rem implements truncated modulus (like Go); see
-// QuoRem for more details.
-func (i Int128) Gop_Rem__1(by Int128) (r Int128) {
-	_, r = i.QuoRem__1(by)
-	return r
-}
-
-func (i Int128) Gop_Rem__0(by int64) (r Int128) {
-	ineg := i.hi&signBit != 0
-	if ineg {
-		i = i.Gop_Neg()
-	}
-	if by < 0 {
-		by = -by
-	}
-
-	n := uint64(by)
-	if i.hi < n {
-		_, r.lo = bits.Div64(i.hi, i.lo, n)
-	} else {
-		_, r.lo = bits.Div64(0, i.hi, n)
-		_, r.lo = bits.Div64(r.lo, i.lo, n)
-	}
-	if ineg {
-		r = r.Gop_Neg()
-	}
-	return r
-}
-
-// -----------------------------------------------------------------------------
-
-func ParseInt128(s string, base int) (out Int128, err error) {
-	b, ok := new(big.Int).SetString(s, base)
-	if !ok {
-		err = fmt.Errorf("invalid int128 string: %q", s)
-		return
-	}
-	out, inRange := Int128_Cast__9(b)
-	if !inRange {
-		err = fmt.Errorf("string %q was not in valid int128 range", s)
-	}
-	return
-}
-
-func FormatInt128(i Int128, base int) string {
-	return i.Text(base)
-}
-
-// -----------------------------------------------------------------------------
diff --git a/builtin/ng/uint128.go b/builtin/ng/uint128.go
deleted file mode 100644
index cfa8ebdf..00000000
--- a/builtin/ng/uint128.go
+++ /dev/null
@@ -1,975 +0,0 @@
-package ng
-
-import (
-	"fmt"
-	"log"
-	"math/big"
-	"math/bits"
-	"strconv"
-)
-
-const (
-	GopPackage = true // to indicate this is a Go+ package
-)
-
-const (
-	Uint128_Max       = 1<<128 - 1
-	Uint128_Min       = 0
-	Uint128_IsUntyped = true
-)
-
-const (
-	maxUint64 = (1 << 64) - 1
-	intSize   = 32 << (^uint(0) >> 63)
-)
-
-//
-// Gop_: Go+ object prefix
-// Gop_xxx_Cast: type Gop_xxx typecast
-// xxxx__N: the Nth overload function
-//
-
-type Gop_ninteger = uint
-
-// -----------------------------------------------------------------------------
-
-type Uint128 struct {
-	hi, lo uint64
-}
-
-// Uint128_Init: func uint128.init(v int) uint128
-func Uint128_Init__0(v int) (out Uint128) {
-	if v < 0 {
-		panic("TODO: can't init uint128 from a negative integer")
-	}
-	return Uint128{lo: uint64(v)}
-}
-
-// Uint128_Init: func bigint.init(v untyped_bigint) bigint
-func Uint128_Init__1(v UntypedBigint) (out Uint128) {
-	out, inRange := Uint128_Cast__9(v)
-	if !inRange {
-		log.Panicf("value %v was not in valid uint128 range\n", v)
-	}
-	return
-}
-
-// Uint128_Cast: func uint128(v untyped_int) uint128
-func Uint128_Cast__0(v int) Uint128 {
-	return Uint128_Cast__6(int64(v))
-}
-
-// Uint128_Cast: func uint128(v untyped_bigint) uint128
-func Uint128_Cast__1(v UntypedBigint) Uint128 {
-	return Uint128_Init__1(v)
-}
-
-// Uint128_Cast: func uint128(v uint64) uint128
-func Uint128_Cast__2(v uint64) Uint128 {
-	return Uint128{lo: v}
-}
-
-// Uint128_Cast: func uint128(v uint32) uint128
-func Uint128_Cast__3(v uint32) Uint128 {
-	return Uint128{lo: uint64(v)}
-}
-
-// Uint128_Cast: func uint128(v uint16) uint128
-func Uint128_Cast__4(v uint16) Uint128 {
-	return Uint128{lo: uint64(v)}
-}
-
-// Uint128_Cast: func uint128(v uint8) uint128
-func Uint128_Cast__5(v uint8) Uint128 {
-	return Uint128{lo: uint64(v)}
-}
-
-// Uint128_Cast: func uint128(v int64) uint128
-func Uint128_Cast__6(v int64) (out Uint128) {
-	if v < 0 {
-		return Uint128{hi: maxUint64, lo: uint64(v)}
-	}
-	return Uint128{lo: uint64(v)}
-}
-
-// Uint128_Cast: func uint128(v int64) (uint128, bool)
-func Uint128_Cast__7(v int64) (out Uint128, inRange bool) {
-	if v < 0 {
-		return
-	}
-	return Uint128{lo: uint64(v)}, true
-}
-
-// Uint128_Cast: func uint128(v *big.Int) uint128
-func Uint128_Cast__8(v *big.Int) Uint128 {
-	out, _ := Uint128_Cast__9(v)
-	return out
-}
-
-// Uint128_Cast: func uint128(v *big.Int) (uint128, bool)
-func Uint128_Cast__9(v *big.Int) (out Uint128, inRange bool) {
-	if v.Sign() < 0 {
-		return out, false
-	}
-
-	words := v.Bits()
-	switch intSize {
-	case 64:
-		lw := len(words)
-		switch lw {
-		case 0:
-			return Uint128{}, true
-		case 1:
-			return Uint128{lo: uint64(words[0])}, true
-		case 2:
-			return Uint128{hi: uint64(words[1]), lo: uint64(words[0])}, true
-		default:
-			return Uint128{hi: maxUint64, lo: maxUint64}, false
-		}
-
-	case 32:
-		lw := len(words)
-		switch lw {
-		case 0:
-			return Uint128{}, true
-		case 1:
-			return Uint128{lo: uint64(words[0])}, true
-		case 2:
-			return Uint128{lo: (uint64(words[1]) << 32) | (uint64(words[0]))}, true
-		case 3:
-			return Uint128{hi: uint64(words[2]), lo: (uint64(words[1]) << 32) | (uint64(words[0]))}, true
-		case 4:
-			return Uint128{
-				hi: (uint64(words[3]) << 32) | (uint64(words[2])),
-				lo: (uint64(words[1]) << 32) | (uint64(words[0])),
-			}, true
-		default:
-			return Uint128{hi: maxUint64, lo: maxUint64}, false
-		}
-
-	default:
-		panic("unsupported bit size")
-	}
-}
-
-// Uint128_Cast: func uint128() uint128
-func Uint128_Cast__a() Uint128 {
-	return Uint128{}
-}
-
-// Uint128_Cast: func uint128(v uint) uint128
-func Uint128_Cast__b(v uint) Uint128 {
-	return Uint128{lo: uint64(v)}
-}
-
-// Uint128_Cast: func uint128(hi, lo uint64) uint128
-func Uint128_Cast__c(hi, lo uint64) Uint128 {
-	return Uint128{hi: hi, lo: lo}
-}
-
-// Gop_Rcast: func float64(v uint128) float64
-func (u Uint128) Gop_Rcast__0() float64 {
-	if u.hi == 0 {
-		return float64(u.lo)
-	}
-	return (float64(u.hi) * (1 << 64)) + float64(u.lo)
-}
-
-// Gop_Rcast: func uint64(v uint128) uint64
-func (u Uint128) Gop_Rcast__1() uint64 {
-	return u.lo
-}
-
-// Gop_Rcast: func uint64(v uint128) (uint64, bool)
-func (u Uint128) Gop_Rcast__2() (out uint64, inRange bool) {
-	return u.lo, u.hi == 0
-}
-
-// Gop_Rcast: func int64(v uint128) int64
-func (u Uint128) Gop_Rcast__3() int64 {
-	return int64(u.lo)
-}
-
-// Gop_Rcast: func int64(v uint128) (int64, bool)
-func (u Uint128) Gop_Rcast__4() (out int64, inRange bool) {
-	return int64(u.lo), u.hi == 0 && u.lo <= maxInt64
-}
-
-// -----------------------------------------------------------------------------
-
-func (u Uint128) IsZero() bool {
-	return u.lo == 0 && u.hi == 0
-}
-
-func (u *Uint128) Scan(state fmt.ScanState, verb rune) (err error) {
-	t, err := state.Token(true, nil)
-	if err != nil {
-		return
-	}
-	v, err := ParseUint128(string(t), 10)
-	if err == nil {
-		*u = v
-	}
-	return
-}
-
-func (u Uint128) Format(s fmt.State, c rune) {
-	// TODO: not so good
-	u.BigInt().Format(s, c)
-}
-
-func (u Uint128) String() string {
-	return u.Text(10)
-}
-
-func (u Uint128) Text(base int) string {
-	if u.hi == 0 {
-		return strconv.FormatUint(u.lo, base)
-	}
-	// TODO: not so good
-	return u.BigInt().Text(base)
-}
-
-func (u Uint128) BigInt() *big.Int {
-	var v big.Int
-	u.ToBigInt(&v)
-	return &v
-}
-
-func (u Uint128) ToBigInt(b *big.Int) {
-	switch intSize {
-	case 64:
-		bits := b.Bits()
-		ln := len(bits)
-		if len(bits) < 2 {
-			bits = append(bits, make([]big.Word, 2-ln)...)
-		}
-		bits = bits[:2]
-		bits[0] = big.Word(u.lo)
-		bits[1] = big.Word(u.hi)
-		b.SetBits(bits)
-
-	case 32:
-		bits := b.Bits()
-		ln := len(bits)
-		if len(bits) < 4 {
-			bits = append(bits, make([]big.Word, 4-ln)...)
-		}
-		bits = bits[:4]
-		bits[0] = big.Word(u.lo & 0xFFFFFFFF)
-		bits[1] = big.Word(u.lo >> 32)
-		bits[2] = big.Word(u.hi & 0xFFFFFFFF)
-		bits[3] = big.Word(u.hi >> 32)
-		b.SetBits(bits)
-
-	default:
-		if u.hi > 0 {
-			b.SetUint64(u.hi)
-			b.Lsh(b, 64)
-		}
-		var lo big.Int
-		lo.SetUint64(u.lo)
-		b.Add(b, &lo)
-	}
-}
-
-// Bit returns the value of the i'th bit of x. That is, it returns (x>>i)&1.
-// The bit index i must be 0 <= i < 128
-func (u Uint128) Bit(i int) uint {
-	if i < 0 || i >= 128 {
-		panic("bit out of range")
-	}
-	if i >= 64 {
-		return uint((u.hi >> uint(i-64)) & 1)
-	} else {
-		return uint((u.lo >> uint(i)) & 1)
-	}
-}
-
-// SetBit returns a Uint128 with u's i'th bit set to b (0 or 1).
-// If b is not 0 or 1, SetBit will panic. If i < 0, SetBit will panic.
-func (u Uint128) SetBit(i int, b uint) (out Uint128) {
-	if i < 0 || i >= 128 {
-		panic("bit out of range")
-	}
-	if b == 0 {
-		if i >= 64 {
-			u.hi = u.hi &^ (1 << uint(i-64))
-		} else {
-			u.lo = u.lo &^ (1 << uint(i))
-		}
-	} else if b == 1 {
-		if i >= 64 {
-			u.hi = u.hi | (1 << uint(i-64))
-		} else {
-			u.lo = u.lo | (1 << uint(i))
-		}
-	} else {
-		panic("bit value not 0 or 1")
-	}
-	return u
-}
-
-func (u Uint128) LeadingZeros() int {
-	if u.hi == 0 {
-		return bits.LeadingZeros64(u.lo) + 64
-	} else {
-		return bits.LeadingZeros64(u.hi)
-	}
-}
-
-func (u Uint128) TrailingZeros() int {
-	if u.lo == 0 {
-		return bits.TrailingZeros64(u.hi) + 64
-	} else {
-		return bits.TrailingZeros64(u.lo)
-	}
-}
-
-// BitLen returns the length of the absolute value of u in bits. The bit length of 0 is 0.
-func (u Uint128) BitLen() int {
-	if u.hi > 0 {
-		return bits.Len64(u.hi) + 64
-	}
-	return bits.Len64(u.lo)
-}
-
-// OnesCount returns the number of one bits ("population count") in u.
-func (u Uint128) OnesCount() int {
-	return bits.OnesCount64(u.hi) + bits.OnesCount64(u.lo)
-}
-
-func (u Uint128) Reverse() Uint128 {
-	return Uint128{hi: bits.Reverse64(u.lo), lo: bits.Reverse64(u.hi)}
-}
-
-func (u Uint128) ReverseBytes() Uint128 {
-	return Uint128{hi: bits.ReverseBytes64(u.lo), lo: bits.ReverseBytes64(u.hi)}
-}
-
-// Cmp compares 'u' to 'n' and returns:
-//
-//	< 0 if u <  n
-//	  0 if u == n
-//	> 0 if u >  n
-//
-// The specific value returned by Cmp is undefined, but it is guaranteed to
-// satisfy the above constraints.
-func (u Uint128) Cmp__1(n Uint128) int {
-	if u.hi == n.hi {
-		if u.lo > n.lo {
-			return 1
-		} else if u.lo < n.lo {
-			return -1
-		}
-	} else {
-		if u.hi > n.hi {
-			return 1
-		} else if u.hi < n.hi {
-			return -1
-		}
-	}
-	return 0
-}
-
-func (u Uint128) Cmp__0(n uint64) int {
-	if u.hi > 0 || u.lo > n {
-		return 1
-	} else if u.lo < n {
-		return -1
-	}
-	return 0
-}
-
-func (u Uint128) Gop_Dup() (v Uint128) {
-	return u
-}
-
-func (u *Uint128) Gop_Inc() {
-	u.lo++
-	if u.lo == 0 {
-		u.hi++
-	}
-}
-
-func (u *Uint128) Gop_Dec() {
-	if u.lo == 0 {
-		u.hi--
-	}
-	u.lo--
-}
-
-// Gop_AddAssign: func (a *uint128) += (b uint128)
-func (u *Uint128) Gop_AddAssign(b Uint128) {
-	*u = u.Gop_Add__1(b)
-}
-
-// Gop_SubAssign: func (a *uint128) -= (b uint128)
-func (u *Uint128) Gop_SubAssign(b Uint128) {
-	*u = u.Gop_Sub__1(b)
-}
-
-// Gop_MulAssign: func (a *uint128) *= (b uint128)
-func (u *Uint128) Gop_MulAssign(b Uint128) {
-	*u = u.Gop_Mul__1(b)
-}
-
-// Gop_QuoAssign: func (a *uint128) /= (b uint128) {
-func (u *Uint128) Gop_QuoAssign(b Uint128) {
-	*u = u.Gop_Quo__1(b)
-}
-
-// Gop_RemAssign: func (a *uint128) %= (b uint128)
-func (u *Uint128) Gop_RemAssign(b Uint128) {
-	*u = u.Gop_Rem__1(b)
-}
-
-// Gop_OrAssign: func (a *uint128) |= (b uint128)
-func (u *Uint128) Gop_OrAssign(b Uint128) {
-	*u = u.Gop_Or__1(b)
-}
-
-// Gop_XorAssign: func (a *uint128) ^= (b uint128)
-func (u *Uint128) Gop_XorAssign(b Uint128) {
-	*u = u.Gop_Xor__1(b)
-}
-
-// Gop_AndAssign: func (a *uint128) &= (b uint128)
-func (u *Uint128) Gop_AndAssign(b Uint128) {
-	*u = u.Gop_And__1(b)
-}
-
-// Gop_AndNotAssign: func (a *uint128) &^= (b uint128)
-func (u *Uint128) Gop_AndNotAssign(b Uint128) {
-	*u = u.Gop_AndNot(b)
-}
-
-// Gop_LshAssign: func (a *uint128) <<= (n untyped_uint)
-func (u *Uint128) Gop_LshAssign(n Gop_ninteger) {
-	*u = u.Gop_Lsh(n)
-}
-
-// Gop_RshAssign: func (a *uint128) >>= (n untyped_uint)
-func (u *Uint128) Gop_RshAssign(n Gop_ninteger) {
-	*u = u.Gop_Rsh(n)
-}
-
-func (u Uint128) Gop_Add__1(n Uint128) (v Uint128) {
-	var carry uint64
-	v.lo, carry = bits.Add64(u.lo, n.lo, 0)
-	v.hi, _ = bits.Add64(u.hi, n.hi, carry)
-	return v
-}
-
-func (u Uint128) Gop_Add__0(n uint64) (v Uint128) {
-	var carry uint64
-	v.lo, carry = bits.Add64(u.lo, n, 0)
-	v.hi = u.hi + carry
-	return v
-}
-
-func (u Uint128) Gop_Sub__1(n Uint128) (v Uint128) {
-	var borrowed uint64
-	v.lo, borrowed = bits.Sub64(u.lo, n.lo, 0)
-	v.hi, _ = bits.Sub64(u.hi, n.hi, borrowed)
-	return v
-}
-
-func (u Uint128) Gop_Sub__0(n uint64) (v Uint128) {
-	var borrowed uint64
-	v.lo, borrowed = bits.Sub64(u.lo, n, 0)
-	v.hi = u.hi - borrowed
-	return v
-}
-
-func (u Uint128) Gop_EQ__1(n Uint128) bool {
-	return u.hi == n.hi && u.lo == n.lo
-}
-
-func (u Uint128) Gop_EQ__0(n uint64) bool {
-	return u.hi == 0 && u.lo == n
-}
-
-func (u Uint128) Gop_GT__1(n Uint128) bool {
-	return u.hi > n.hi || (u.hi == n.hi && u.lo > n.lo)
-}
-
-func (u Uint128) Gop_GT__0(n uint64) bool {
-	return u.hi > 0 || u.lo > n
-}
-
-func (u Uint128) Gop_GE__1(n Uint128) bool {
-	return u.hi > n.hi || (u.hi == n.hi && u.lo >= n.lo)
-}
-
-func (u Uint128) Gop_GE__0(n uint64) bool {
-	return u.hi > 0 || u.lo >= n
-}
-
-func (u Uint128) Gop_LT__1(n Uint128) bool {
-	return u.hi < n.hi || (u.hi == n.hi && u.lo < n.lo)
-}
-
-func (u Uint128) Gop_LT__0(n uint64) bool {
-	return u.hi == 0 && u.lo < n
-}
-
-func (u Uint128) Gop_LE__1(n Uint128) bool {
-	return u.hi < n.hi || (u.hi == n.hi && u.lo <= n.lo)
-}
-
-func (u Uint128) Gop_LE__0(n uint64) bool {
-	return u.hi == 0 && u.lo <= n
-}
-
-func (u Uint128) Gop_And__1(n Uint128) Uint128 {
-	u.hi &= n.hi
-	u.lo &= n.lo
-	return u
-}
-
-func (u Uint128) Gop_And__0(n uint64) Uint128 {
-	return Uint128{lo: u.lo & n}
-}
-
-func (u Uint128) Gop_AndNot(n Uint128) Uint128 {
-	u.hi &^= n.hi
-	u.lo &^= n.lo
-	return u
-}
-
-func (u Uint128) Gop_Not() Uint128 {
-	return Uint128{hi: ^u.hi, lo: ^u.lo}
-}
-
-func (u Uint128) Gop_Or__1(n Uint128) Uint128 {
-	u.hi |= n.hi
-	u.lo |= n.lo
-	return u
-}
-
-func (u Uint128) Gop_Or__0(n uint64) Uint128 {
-	u.lo |= n
-	return u
-}
-
-func (u Uint128) Gop_Xor__1(v Uint128) Uint128 {
-	u.hi ^= v.hi
-	u.lo ^= v.lo
-	return u
-}
-
-func (u Uint128) Gop_Xor__0(v uint64) Uint128 {
-	u.lo ^= v
-	return u
-}
-
-func (u Uint128) Gop_Lsh(n Gop_ninteger) Uint128 {
-	if n < 64 {
-		u.hi = (u.hi << n) | (u.lo >> (64 - n))
-		u.lo <<= n
-	} else {
-		u.hi = u.lo << (n - 64)
-		u.lo = 0
-	}
-	return u
-}
-
-func (u Uint128) Gop_Rsh(n Gop_ninteger) Uint128 {
-	if n < 64 {
-		u.lo = (u.lo >> n) | (u.hi << (64 - n))
-		u.hi >>= n
-	} else {
-		u.lo = u.hi >> (n - 64)
-		u.hi = 0
-	}
-	return u
-}
-
-func (u Uint128) Gop_Mul__1(n Uint128) Uint128 {
-	hi, lo := bits.Mul64(u.lo, n.lo)
-	hi += u.hi*n.lo + u.lo*n.hi
-	return Uint128{hi, lo}
-}
-
-func (u Uint128) Gop_Mul__0(n uint64) (dest Uint128) {
-	dest.hi, dest.lo = bits.Mul64(u.lo, n)
-	dest.hi += u.hi * n
-	return dest
-}
-
-const (
-	divAlgoLeading0Spill = 16
-)
-
-func (u Uint128) Gop_Quo__1(by Uint128) (q Uint128) {
-	if by.lo == 0 && by.hi == 0 {
-		panic("division by zero")
-	}
-
-	if u.hi|by.hi == 0 {
-		q.lo = u.lo / by.lo // FIXME: div/0 risk?
-		return q
-	}
-
-	var byLoLeading0, byHiLeading0, byLeading0 uint
-	if by.hi == 0 {
-		byLoLeading0, byHiLeading0 = uint(bits.LeadingZeros64(by.lo)), 64
-		byLeading0 = byLoLeading0 + 64
-	} else {
-		byHiLeading0 = uint(bits.LeadingZeros64(by.hi))
-		byLeading0 = byHiLeading0
-	}
-
-	if byLeading0 == 127 {
-		return u
-	}
-
-	byTrailing0 := uint(by.TrailingZeros())
-	if (byLeading0 + byTrailing0) == 127 {
-		return u.Gop_Rsh(byTrailing0)
-	}
-
-	if cmp := u.Cmp__1(by); cmp < 0 {
-		return q // it's 100% remainder
-	} else if cmp == 0 {
-		q.lo = 1 // dividend and divisor are the same
-		return q
-	}
-
-	uLeading0 := uint(u.LeadingZeros())
-	if byLeading0-uLeading0 > divAlgoLeading0Spill {
-		q, _ = quorem128by128(u, by, byHiLeading0, byLoLeading0)
-		return q
-	} else {
-		return quo128bin(u, by, uLeading0, byLeading0)
-	}
-}
-
-func (u Uint128) Gop_Quo__0(by uint64) (q Uint128) {
-	if u.hi < by {
-		q.lo, _ = bits.Div64(u.hi, u.lo, by)
-	} else {
-		q.hi = u.hi / by
-		q.lo, _ = bits.Div64(u.hi%by, u.lo, by)
-	}
-	return q
-}
-
-func (u Uint128) QuoRem__1(by Uint128) (q, r Uint128) {
-	if by.lo == 0 && by.hi == 0 {
-		panic("division by zero")
-	}
-
-	if u.hi|by.hi == 0 {
-		q.lo = u.lo / by.lo
-		r.lo = u.lo % by.lo
-		return q, r
-	}
-
-	var byLoLeading0, byHiLeading0, byLeading0 uint
-	if by.hi == 0 {
-		byLoLeading0, byHiLeading0 = uint(bits.LeadingZeros64(by.lo)), 64
-		byLeading0 = byLoLeading0 + 64
-	} else {
-		byHiLeading0 = uint(bits.LeadingZeros64(by.hi))
-		byLeading0 = byHiLeading0
-	}
-
-	if byLeading0 == 127 {
-		return u, r
-	}
-
-	byTrailing0 := uint(by.TrailingZeros())
-	if (byLeading0 + byTrailing0) == 127 {
-		q = u.Gop_Rsh(byTrailing0)
-		by.Gop_Dec()
-		r = by.Gop_And__1(u)
-		return
-	}
-
-	if cmp := u.Cmp__1(by); cmp < 0 {
-		return q, u // it's 100% remainder
-
-	} else if cmp == 0 {
-		q.lo = 1 // dividend and divisor are the same
-		return q, r
-	}
-
-	uLeading0 := uint(u.LeadingZeros())
-	if byLeading0-uLeading0 > divAlgoLeading0Spill {
-		return quorem128by128(u, by, byHiLeading0, byLoLeading0)
-	} else {
-		return quorem128bin(u, by, uLeading0, byLeading0)
-	}
-}
-
-func (u Uint128) QuoRem__0(by uint64) (q, r Uint128) {
-	if u.hi < by {
-		q.lo, r.lo = bits.Div64(u.hi, u.lo, by)
-	} else {
-		q.hi, r.lo = bits.Div64(0, u.hi, by)
-		q.lo, r.lo = bits.Div64(r.lo, u.lo, by)
-	}
-	return q, r
-}
-
-// Gop_Rem: func (a uint128) % (b uint128) uint128
-func (u Uint128) Gop_Rem__1(by Uint128) (r Uint128) {
-	// TODO: inline only the needed bits
-	_, r = u.QuoRem__1(by)
-	return r
-}
-
-func (u Uint128) Gop_Rem__0(by uint64) (r Uint128) {
-	// https://github.com/golang/go/issues/28970
-	// if u.hi < by {
-	//     _, r.lo = bits.Rem64(u.hi, u.lo, by)
-	// } else {
-	//     _, r.lo = bits.Rem64(bits.Rem64(0, u.hi, by), u.lo, by)
-	// }
-
-	if u.hi < by {
-		_, r.lo = bits.Div64(u.hi, u.lo, by)
-	} else {
-		_, r.lo = bits.Div64(0, u.hi, by)
-		_, r.lo = bits.Div64(r.lo, u.lo, by)
-	}
-	return r
-}
-
-// Hacker's delight 9-4, divlu:
-func quo128by64(u1, u0, v uint64, vLeading0 uint) (q uint64) {
-	var b uint64 = 1 << 32
-	var un1, un0, vn1, vn0, q1, q0, un32, un21, un10, rhat, vs, left, right uint64
-
-	vs = v << vLeading0
-
-	vn1 = vs >> 32
-	vn0 = vs & 0xffffffff
-
-	if vLeading0 > 0 {
-		un32 = (u1 << vLeading0) | (u0 >> (64 - vLeading0))
-		un10 = u0 << vLeading0
-	} else {
-		un32 = u1
-		un10 = u0
-	}
-
-	un1 = un10 >> 32
-	un0 = un10 & 0xffffffff
-
-	q1 = un32 / vn1
-	rhat = un32 % vn1
-
-	left = q1 * vn0
-	right = (rhat << 32) | un1
-
-again1:
-	if (q1 >= b) || (left > right) {
-		q1--
-		rhat += vn1
-		if rhat < b {
-			left -= vn0
-			right = (rhat << 32) | un1
-			goto again1
-		}
-	}
-
-	un21 = (un32 << 32) + (un1 - (q1 * vs))
-
-	q0 = un21 / vn1
-	rhat = un21 % vn1
-
-	left = q0 * vn0
-	right = (rhat << 32) | un0
-
-again2:
-	if (q0 >= b) || (left > right) {
-		q0--
-		rhat += vn1
-		if rhat < b {
-			left -= vn0
-			right = (rhat << 32) | un0
-			goto again2
-		}
-	}
-
-	return (q1 << 32) | q0
-}
-
-// Hacker's delight 9-4, divlu:
-func quorem128by64(u1, u0, v uint64, vLeading0 uint) (q, r uint64) {
-	var b uint64 = 1 << 32
-	var un1, un0, vn1, vn0, q1, q0, un32, un21, un10, rhat, left, right uint64
-
-	v <<= vLeading0
-
-	vn1 = v >> 32
-	vn0 = v & 0xffffffff
-
-	if vLeading0 > 0 {
-		un32 = (u1 << vLeading0) | (u0 >> (64 - vLeading0))
-		un10 = u0 << vLeading0
-	} else {
-		un32 = u1
-		un10 = u0
-	}
-
-	un1 = un10 >> 32
-	un0 = un10 & 0xffffffff
-
-	q1 = un32 / vn1
-	rhat = un32 % vn1
-
-	left = q1 * vn0
-	right = (rhat << 32) + un1
-
-again1:
-	if (q1 >= b) || (left > right) {
-		q1--
-		rhat += vn1
-		if rhat < b {
-			left -= vn0
-			right = (rhat << 32) | un1
-			goto again1
-		}
-	}
-
-	un21 = (un32 << 32) + (un1 - (q1 * v))
-
-	q0 = un21 / vn1
-	rhat = un21 % vn1
-
-	left = q0 * vn0
-	right = (rhat << 32) | un0
-
-again2:
-	if (q0 >= b) || (left > right) {
-		q0--
-		rhat += vn1
-		if rhat < b {
-			left -= vn0
-			right = (rhat << 32) | un0
-			goto again2
-		}
-	}
-
-	return (q1 << 32) | q0, ((un21 << 32) + (un0 - (q0 * v))) >> vLeading0
-}
-
-func quorem128by128(m, v Uint128, vHiLeading0, vLoLeading0 uint) (q, r Uint128) {
-	if v.hi == 0 {
-		if m.hi < v.lo {
-			q.lo, r.lo = quorem128by64(m.hi, m.lo, v.lo, vLoLeading0)
-			return q, r
-
-		} else {
-			q.hi = m.hi / v.lo
-			r.hi = m.hi % v.lo
-			q.lo, r.lo = quorem128by64(r.hi, m.lo, v.lo, vLoLeading0)
-			r.hi = 0
-			return q, r
-		}
-
-	} else {
-		v1 := v.Gop_Lsh(vHiLeading0)
-		u1 := m.Gop_Rsh(1)
-
-		var q1 Uint128
-		q1.lo = quo128by64(u1.hi, u1.lo, v1.hi, vLoLeading0)
-		q1 = q1.Gop_Rsh(63 - vHiLeading0)
-
-		if q1.hi|q1.lo != 0 {
-			q1.Gop_Dec()
-		}
-		q = q1
-		q1 = q1.Gop_Mul__1(v)
-		r = m.Gop_Sub__1(q1)
-
-		if r.Cmp__1(v) >= 0 {
-			q.Gop_Inc()
-			r = r.Gop_Sub__1(v)
-		}
-
-		return q, r
-	}
-}
-
-func quorem128bin(u, by Uint128, uLeading0, byLeading0 uint) (q, r Uint128) {
-	shift := int(byLeading0 - uLeading0)
-	by = by.Gop_Lsh(uint(shift))
-
-	for {
-		// q << 1
-		q.hi = (q.hi << 1) | (q.lo >> 63)
-		q.lo = q.lo << 1
-
-		// performance tweak: simulate greater than or equal by hand-inlining "not less than".
-		if u.hi > by.hi || (u.hi == by.hi && u.lo >= by.lo) {
-			u = u.Gop_Sub__1(by)
-			q.lo |= 1
-		}
-
-		// by >> 1
-		by.lo = (by.lo >> 1) | (by.hi << 63)
-		by.hi = by.hi >> 1
-
-		if shift <= 0 {
-			break
-		}
-		shift--
-	}
-
-	r = u
-	return q, r
-}
-
-func quo128bin(u, by Uint128, uLeading0, byLeading0 uint) (q Uint128) {
-	shift := int(byLeading0 - uLeading0)
-	by = by.Gop_Lsh(uint(shift))
-
-	for {
-		// q << 1
-		q.hi = (q.hi << 1) | (q.lo >> 63)
-		q.lo = q.lo << 1
-
-		// u >= by
-		if u.hi > by.hi || (u.hi == by.hi && u.lo >= by.lo) {
-			u = u.Gop_Sub__1(by)
-			q.lo |= 1
-		}
-
-		// q >> 1
-		by.lo = (by.lo >> 1) | (by.hi << 63)
-		by.hi = by.hi >> 1
-
-		if shift <= 0 {
-			break
-		}
-		shift--
-	}
-
-	return q
-}
-
-// -----------------------------------------------------------------------------
-
-func ParseUint128(s string, base int) (out Uint128, err error) {
-	b, ok := new(big.Int).SetString(s, base)
-	if !ok {
-		err = fmt.Errorf("invalid uint128 string: %q", s)
-		return
-	}
-	out, inRange := Uint128_Cast__9(b)
-	if !inRange {
-		err = fmt.Errorf("string %q was not in valid uint128 range", s)
-	}
-	return
-}
-
-func FormatUint128(i Uint128, base int) string {
-	return i.Text(base)
-}
-
-// -----------------------------------------------------------------------------
diff --git a/builtin/range.go b/builtin/range.go
deleted file mode 100644
index 259104a1..00000000
--- a/builtin/range.go
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2021 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 builtin
-
-const (
-	GopPackage = true // to indicate this is a Go+ package
-)
-
-// -----------------------------------------------------------------------------
-
-type IntRange struct {
-	Start, End, Step int
-}
-
-func NewRange__0(start, end, step int) *IntRange {
-	return &IntRange{Start: start, End: end, Step: step}
-}
-
-func (p *IntRange) Gop_Enum() *intRangeIter {
-	step := p.Step
-	n := p.End - p.Start + step
-	if step > 0 {
-		n = (n - 1) / step
-	} else {
-		n = (n + 1) / step
-	}
-	return &intRangeIter{n: n, val: p.Start, step: p.Step}
-}
-
-// -----------------------------------------------------------------------------
-
-type intRangeIter struct {
-	n, val, step int
-}
-
-func (p *intRangeIter) Next() (val int, ok bool) {
-	if p.n > 0 {
-		val, ok = p.val, true
-		p.val += p.step
-		p.n--
-	}
-	return
-}
-
-// -----------------------------------------------------------------------------
diff --git a/builtin/stringslice/stringslice.go b/builtin/stringslice/stringslice.go
deleted file mode 100644
index e2eb9823..00000000
--- a/builtin/stringslice/stringslice.go
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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 stringslice
-
-import (
-	"strings"
-
-	"github.com/qiniu/x/stringutil"
-)
-
-// Capitalize capitalizes the first letter of each string in the slice.
-func Capitalize(a []string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = stringutil.Capitalize(v)
-	}
-	return r
-}
-
-// ToTitle title-cases all strings in the slice.
-func ToTitle(a []string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.ToTitle(v)
-	}
-	return r
-}
-
-// ToUpper upper-cases all strings in the slice.
-func ToUpper(a []string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.ToUpper(v)
-	}
-	return r
-}
-
-// ToLower lower-cases all strings in the slice.
-func ToLower(a []string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.ToLower(v)
-	}
-	return r
-}
-
-// Repeat repeats each string in the slice count times.
-func Repeat(a []string, count int) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.Repeat(v, count)
-	}
-	return r
-}
-
-// Replace replaces all occurrences of old in each string in the slice with new.
-func Replace(a []string, old, new string, n int) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.Replace(v, old, new, n)
-	}
-	return r
-}
-
-// ReplaceAll replaces all occurrences of old in each string in the slice with new.
-func ReplaceAll(a []string, old, new string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.ReplaceAll(v, old, new)
-	}
-	return r
-}
-
-// Trim removes leading and trailing white space from each string in the slice.
-func Trim(a []string, cutset string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.Trim(v, cutset)
-	}
-	return r
-}
-
-// TrimSpace removes leading and trailing white space from each string in the slice.
-func TrimSpace(a []string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.TrimSpace(v)
-	}
-	return r
-}
-
-// TrimLeft removes leading white space from each string in the slice.
-func TrimLeft(a []string, cutset string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.TrimLeft(v, cutset)
-	}
-	return r
-}
-
-// TrimRight removes trailing white space from each string in the slice.
-func TrimRight(a []string, cutset string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.TrimRight(v, cutset)
-	}
-	return r
-}
-
-// TrimPrefix removes leading prefix from each string in the slice.
-func TrimPrefix(a []string, prefix string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.TrimPrefix(v, prefix)
-	}
-	return r
-}
-
-// TrimSuffix removes trailing suffix from each string in the slice.
-func TrimSuffix(a []string, suffix string) []string {
-	r := make([]string, len(a))
-	for i, v := range a {
-		r[i] = strings.TrimSuffix(v, suffix)
-	}
-	return r
-}
diff --git a/cl/builtin.go b/cl/builtin.go
index 6becb512..176f76d5 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -38,7 +38,7 @@ func initBuiltinFns(builtin *types.Package, scope *types.Scope, pkg gogen.PkgRef
 	}
 }
 
-func initBuiltin(_ *gogen.Package, builtin *types.Package, os, fmt, ng, iox, buil, reflect gogen.PkgRef) {
+func initBuiltin(_ *gogen.Package, builtin *types.Package, os, fmt, ng, osx, buil, reflect gogen.PkgRef) {
 	scope := builtin.Scope()
 	if ng.Types != nil {
 		typs := []string{"bigint", "bigrat", "bigfloat"}
@@ -62,11 +62,11 @@ func initBuiltin(_ *gogen.Package, builtin *types.Package, os, fmt, ng, iox, bui
 			"open", "create",
 		})
 	}
-	if iox.Types != nil {
-		initBuiltinFns(builtin, scope, iox, []string{
-			"lines",
+	if osx.Types != nil {
+		initBuiltinFns(builtin, scope, osx, []string{
+			"lines", "errorln", "fatal",
 		})
-		scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "blines", iox.Ref("BLines")))
+		scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "blines", osx.Ref("BLines")))
 	}
 	if reflect.Types != nil {
 		scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "type", reflect.Ref("TypeOf")))
@@ -77,16 +77,20 @@ func initBuiltin(_ *gogen.Package, builtin *types.Package, os, fmt, ng, iox, bui
 	scope.Insert(types.NewTypeName(token.NoPos, builtin, "any", gogen.TyEmptyInterface))
 }
 
+const (
+	osxPkgPath = "github.com/qiniu/x/gop/osx"
+)
+
 func newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
 	builtin := types.NewPackage("", "")
 	fmt := pkg.TryImport("fmt")
 	os := pkg.TryImport("os")
 	reflect := pkg.TryImport("reflect")
-	buil := pkg.TryImport("github.com/goplus/gop/builtin")
-	ng := pkg.TryImport("github.com/goplus/gop/builtin/ng")
-	iox := pkg.TryImport("github.com/goplus/gop/builtin/iox")
+	osx := pkg.TryImport(osxPkgPath)
+	buil := pkg.TryImport("github.com/qiniu/x/gop")
+	ng := pkg.TryImport("github.com/qiniu/x/gop/ng")
 	strx := pkg.TryImport("github.com/qiniu/x/stringutil")
-	stringslice := pkg.TryImport("github.com/goplus/gop/builtin/stringslice")
+	stringslice := pkg.TryImport("github.com/qiniu/x/stringslice")
 	pkg.TryImport("strconv")
 	pkg.TryImport("strings")
 	if ng.Types != nil {
@@ -97,7 +101,7 @@ func newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
 			}
 		}
 	}
-	initBuiltin(pkg, builtin, os, fmt, ng, iox, buil, reflect)
+	initBuiltin(pkg, builtin, os, fmt, ng, osx, buil, reflect)
 	gogen.InitBuiltin(pkg, builtin, conf)
 	if strx.Types != nil {
 		ti := pkg.BuiltinTI(types.Typ[types.String])
diff --git a/cl/classfile.go b/cl/classfile.go
index 964b6b00..0b70acc1 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -24,6 +24,7 @@ import (
 	"log"
 	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 
 	"github.com/goplus/gogen"
@@ -36,27 +37,29 @@ import (
 // -----------------------------------------------------------------------------
 
 type gmxClass struct {
-	tname_  string // class type
+	name    string // class type, empty for default project class
 	clsfile string
 	ext     string
 	proj    *gmxProject
+	sp      *spxObj
 }
 
 func (p *gmxClass) getName(ctx *pkgCtx) string {
-	tname := p.tname_
-	if tname == "main" {
+	tname := p.name
+	if tname == "" { // use default project class
 		tname = p.proj.getGameClass(ctx)
 	}
 	return tname
 }
 
 type spxObj struct {
-	obj   gogen.Ref
-	ext   string
-	proto string
-	feats spriteFeat
-	clone *types.Signature // prototype of Classclone
-	types []string         // <type>.spx
+	obj    gogen.Ref // work base class
+	ext    string
+	proto  string           // work class prototype
+	prefix string           // work class prefix
+	feats  spriteFeat       // work class features
+	clone  *types.Signature // prototype of Classclone
+	types  []string         // work classes (ie. <type>.spx)
 }
 
 func spriteByProto(sprites []*spxObj, proto string) *spxObj {
@@ -84,13 +87,17 @@ type gmxProject struct {
 	hasMain_   bool
 }
 
-func (p *gmxProject) spriteOf(ext string) *spxObj {
+func (p *gmxProject) embed(flds []*types.Var, pkg *gogen.Package) []*types.Var {
 	for _, sp := range p.sprites {
-		if sp.ext == ext {
-			return sp
+		if sp.feats&spriteEmbedded != 0 {
+			for _, spt := range sp.types {
+				spto := pkg.Ref(spt)                // work class
+				pt := types.NewPointer(spto.Type()) // pointer to work class
+				flds = append(flds, types.NewField(token.NoPos, pkg.Types, spto.Name(), pt, false))
+			}
 		}
 	}
-	return nil
+	return flds
 }
 
 type spriteFeat uint
@@ -98,6 +105,8 @@ type spriteFeat uint
 const (
 	spriteClassfname spriteFeat = 1 << iota
 	spriteClassclone
+
+	spriteEmbedded spriteFeat = 0x80
 )
 
 func spriteFeature(elt types.Type, sp *spxObj) {
@@ -145,12 +154,12 @@ func spriteFeatures(game gogen.Ref, sprites []*spxObj) {
 }
 
 func (p *gmxProject) getGameClass(ctx *pkgCtx) string {
-	tname := p.gameClass_
+	tname := p.gameClass_ // project class
 	if tname != "" && tname != "main" {
 		return tname
 	}
 	gt := p.gt
-	tname = gt.Class
+	tname = gt.Class // project base class
 	if p.gameIsPtr {
 		tname = tname[1:]
 	}
@@ -200,6 +209,9 @@ func ClassNameAndExt(file string) (name, clsfile, ext string) {
 }
 
 // GetFileClassType get ast.File classType
+// TODO(xsw): to refactor
+//
+// Deprecated: Don't use it
 func GetFileClassType(file *ast.File, filename string, lookupClass func(ext string) (c *Project, ok bool)) (classType string, isTest bool) {
 	if file.IsClass {
 		var ext string
@@ -264,11 +276,15 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 		nWork := len(gt.Works)
 		sprites := make([]*spxObj, nWork)
 		for i, v := range gt.Works {
-			obj, _ := spxRef(spx, v.Class)
-			sprites[i] = &spxObj{obj: obj, ext: v.Ext, proto: v.Proto}
 			if nWork > 1 && v.Proto == "" {
 				panic("should have prototype if there are multiple work classes")
 			}
+			obj, _ := spxRef(spx, v.Class)
+			sp := &spxObj{obj: obj, ext: v.Ext, proto: v.Proto, prefix: v.Prefix}
+			if v.Embedded {
+				sp.feats |= spriteEmbedded
+			}
+			sprites[i] = sp
 		}
 		p.sprites = sprites
 		if gt.Class != "" {
@@ -279,6 +295,7 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 			p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true
 		}
 	}
+	cls := &gmxClass{clsfile: clsfile, ext: ext, proj: p}
 	if f.IsProj {
 		if p.gameClass_ != "" {
 			panic("multiple project files found: " + tname + ", " + p.gameClass_)
@@ -288,17 +305,51 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 		if !p.isTest {
 			ctx.nproj++
 		}
+		if tname != "main" {
+			cls.name = tname
+		}
 	} else {
-		sp := p.spriteOf(ext)
+		sp := getSpxObj(p, ext)
+		tname := spName(sp, tname)
 		sp.types = append(sp.types, tname)
+		cls.sp = sp
+		cls.name = tname
 	}
-	ctx.classes[f] = &gmxClass{tname, clsfile, ext, p}
+	ctx.classes[f] = cls
 	if debugLoad {
 		log.Println("==> InitClass", tname, "isProj:", f.IsProj)
 	}
 	return p
 }
 
+type none = struct{}
+
+var specialNames = map[string]none{
+	"init": {}, "main": {}, "go": {}, "goto": {}, "type": {}, "var": {}, "import": {},
+	"package": {}, "interface": {}, "struct": {}, "const": {}, "func": {}, "map": {},
+	"for": {}, "if": {}, "else": {}, "switch": {}, "case": {}, "select": {}, "defer": {},
+	"range": {}, "return": {}, "break": {}, "continue": {}, "fallthrough": {}, "default": {},
+}
+
+func spName(sp *spxObj, name string) string {
+	if sp.prefix != "" {
+		return sp.prefix + name
+	}
+	if _, ok := specialNames[name]; ok {
+		name = "_" + name
+	}
+	return name
+}
+
+func getSpxObj(p *gmxProject, ext string) *spxObj {
+	for _, sp := range p.sprites {
+		if sp.ext == ext {
+			return sp
+		}
+	}
+	return nil
+}
+
 func spxLookup(pkgImps []gogen.PkgRef, name string) gogen.Ref {
 	for _, pkg := range pkgImps {
 		if o := pkg.TryRef(name); o != nil {
@@ -410,10 +461,10 @@ func gmxCheckProjs(pkg *gogen.Package, ctx *pkgCtx) (*gmxProject, bool) {
 }
 
 func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
-	base := proj.game
-	classType := proj.getGameClass(parent)
+	base := proj.game                      // project base class
+	classType := proj.getGameClass(parent) // project class
 	ld := getTypeLoader(parent, parent.syms, token.NoPos, classType)
-	if ld.typ == nil {
+	if ld.typ == nil { // no project class, use default
 		ld.typ = func() {
 			if debugLoad {
 				log.Println("==> Load > NewType", classType)
@@ -425,10 +476,11 @@ func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
 			if proj.gameIsPtr {
 				baseType = types.NewPointer(baseType)
 			}
-			name := base.Name()
-			flds := []*types.Var{
-				types.NewField(token.NoPos, pkg.Types, name, baseType, true),
-			}
+
+			flds := proj.embed([]*types.Var{
+				types.NewField(token.NoPos, pkg.Types, base.Name(), baseType, true),
+			}, pkg)
+
 			decl := pkg.NewTypeDefs().NewType(classType)
 			ld.typInit = func() { // decycle
 				if debugLoad {
@@ -456,45 +508,53 @@ func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
 			defer pkg.RestoreCurFile(old)
 
 			cb := fn.BodyStart(pkg).Typ(base.Type()).MemberVal("Main")
+			stk := cb.InternalStack()
 
 			// force remove //line comments for main func
 			cb.SetComments(nil, false)
 
-			sigParams := cb.Get(-1).Type.(*types.Signature).Params()
-			if _, ok := sigParams.At(0).Type().(*types.Pointer); !ok {
-				cb.Val(recv) // template recv method
-			} else {
-				cb.Val(recv).MemberRef(base.Name()).UnaryOp(gotoken.AND)
+			mainFn := stk.Pop()
+			sigParams := mainFn.Type.(*types.Signature).Params()
+			callMain := func() {
+				stk.Push(mainFn)
+				if _, isPtr := sigParams.At(0).Type().(*types.Pointer); isPtr {
+					cb.Val(recv).MemberRef(base.Name()).UnaryOp(gotoken.AND)
+				} else {
+					cb.Val(recv) // template recv method
+				}
 			}
 
+			iobj := 0
 			narg := sigParams.Len()
 			if narg > 1 {
 				sprites := proj.sprites
-				if len(sprites) == 1 && sprites[0].proto == "" {
+				if len(sprites) == 1 && sprites[0].proto == "" { // no work class prototype
 					sp := sprites[0]
 					narg = 1 + len(sp.types)
-					new := pkg.Builtin().Ref("new")
-					for _, spt := range sp.types {
-						spto := pkg.Ref(spt)
-						cb.Val(new).Val(spto).Call(1)
-					}
+					genWorkClasses(pkg, cb, recv, sp, iobj, -1, callMain)
 				} else {
-					new := pkg.Builtin().Ref("new")
+					lstNames := make([]string, narg)
 					for i := 1; i < narg; i++ {
 						tslice := sigParams.At(i).Type()
 						tn := tslice.(*types.Slice).Elem().(*types.Named)
-						sp := spriteByProto(sprites, tn.Obj().Name())
+						sp := spriteByProto(sprites, tn.Obj().Name()) // work class
 						if n := len(sp.types); n > 0 {
-							for _, spt := range sp.types {
-								spto := pkg.Ref(spt)
-								cb.Val(new).Val(spto).Call(1)
-							}
-							cb.SliceLitEx(tslice, n, false)
+							lstNames[i] = genWorkClasses(pkg, cb, recv, sp, iobj, i, nil)
+							cb.SliceLitEx(tslice, n, false).EndInit(1)
+							iobj += n
+						}
+					}
+					callMain()
+					for i := 1; i < narg; i++ {
+						if lstName := lstNames[i]; lstName != "" {
+							cb.VarVal(lstName)
 						} else {
 							cb.Val(nil)
 						}
 					}
 				}
+			} else {
+				callMain()
 			}
 
 			cb.Call(narg).EndStmt().End()
@@ -502,8 +562,43 @@ func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
 	})
 }
 
+func genWorkClasses(
+	pkg *gogen.Package, cb *gogen.CodeBuilder, recv *types.Var,
+	sp *spxObj, iobj, ilst int, callMain func()) (lstName string) {
+	const (
+		indexGame     = 1
+		objNamePrefix = "_gop_obj"
+		lstNamePrefix = "_gop_lst"
+	)
+	embedded := (sp.feats&spriteEmbedded != 0)
+	sptypes := sp.types
+	for i, spt := range sptypes {
+		spto := pkg.Ref(spt)
+		objName := objNamePrefix + strconv.Itoa(iobj+i)
+		cb.DefineVarStart(token.NoPos, objName).
+			Val(indexGame).Val(recv).StructLit(spto.Type(), 2, true).
+			UnaryOp(gotoken.AND).EndInit(1)
+		if embedded {
+			cb.Val(recv).MemberRef(spt).VarVal(objName).Assign(1)
+		}
+	}
+	if ilst > 0 {
+		lstName = lstNamePrefix + strconv.Itoa(ilst-1)
+		cb.DefineVarStart(token.NoPos, lstName)
+	} else {
+		callMain()
+	}
+	for i := range sptypes {
+		objName := objNamePrefix + strconv.Itoa(iobj+i)
+		cb.VarVal(objName)
+	}
+	return
+}
+
 func genMainFunc(pkg *gogen.Package, gameClass string) {
 	if o := pkg.TryRef(gameClass); o != nil {
+		// force remove //line comments for main func
+		pkg.CB().SetComments(nil, false)
 		// new(gameClass).Main()
 		new := pkg.Builtin().Ref("new")
 		pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg).
@@ -528,17 +623,16 @@ func findMethod(o types.Object, name string) *types.Func {
 
 func makeMainSig(recv *types.Var, f *types.Func) *types.Signature {
 	const (
-		paramNameTempl = "_gop_arg0"
+		namePrefix = "_gop_arg"
 	)
 	sig := f.Type().(*types.Signature)
 	in := sig.Params()
 	nin := in.Len()
 	pkg := recv.Pkg()
 	params := make([]*types.Var, nin)
-	paramName := []byte(paramNameTempl)
 	for i := 0; i < nin; i++ {
-		paramName[len(paramNameTempl)-1] = byte('0' + i)
-		params[i] = types.NewParam(token.NoPos, pkg, string(paramName), in.At(i).Type())
+		paramName := namePrefix + strconv.Itoa(i)
+		params[i] = types.NewParam(token.NoPos, pkg, paramName, in.At(i).Type())
 	}
 	return types.NewSignatureType(recv, nil, nil, types.NewTuple(params...), sig.Results(), false)
 }
diff --git a/cl/compile.go b/cl/compile.go
index 385b6e22..e2c05495 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -489,10 +489,6 @@ const (
 	testingGoFile  = "_test"
 )
 
-const (
-	ioxPkgPath = "github.com/goplus/gop/builtin/iox"
-)
-
 // NewPackage creates a Go+ package instance.
 func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Package, err error) {
 	relBaseDir := conf.RelativeBase
@@ -520,7 +516,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		NewBuiltin:      newBuiltinDefault,
 		DefaultGoFile:   defaultGoFile,
 		NoSkipConstant:  conf.NoSkipConstant,
-		PkgPathIox:      ioxPkgPath,
+		PkgPathIox:      osxPkgPath,
 		DbgPositioner:   interp,
 	}
 	var rec *goxRecorder
@@ -734,9 +730,6 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 	if f.IsClass {
 		if f.IsNormalGox {
 			classType, _, _ = ClassNameAndExt(file)
-			if classType == "main" {
-				classType = "_main"
-			}
 			if f.ShadowEntry != nil {
 				parent.goxMainClass = classType
 				parent.goxMain++
@@ -762,7 +755,7 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 					baseType = types.NewPointer(baseType)
 				}
 			} else {
-				sp = proj.spriteOf(c.ext)
+				sp = c.sp
 				o := sp.obj
 				ctx.baseClass = o
 				baseTypeName, baseType = o.Name(), o.Type()
@@ -803,22 +796,24 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 				var flds []*types.Var
 				var tags []string
 				chk := newCheckRedecl()
-				if baseTypeName != "" {
+				if baseTypeName != "" { // base class (not normal classfile)
 					flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
 					tags = append(tags, "")
 					chk.chkRedecl(ctx, baseTypeName, pos)
-				}
-				if sp != nil && !goxTestFile {
-					if gameClass != "" {
-						typ := toType(ctx, &ast.Ident{Name: gameClass})
-						getUnderlying(ctx, typ) // ensure type is loaded
-						typ = types.NewPointer(typ)
-						name := getTypeName(typ)
-						if !chk.chkRedecl(ctx, name, pos) {
-							fld := types.NewField(pos, pkg, name, typ, true)
-							flds = append(flds, fld)
-							tags = append(tags, "")
+					if sp != nil { // for work class
+						if !goxTestFile && gameClass != "" { // has project class
+							typ := toType(ctx, &ast.Ident{Name: gameClass})
+							getUnderlying(ctx, typ) // ensure type is loaded
+							typ = types.NewPointer(typ)
+							name := getTypeName(typ)
+							if !chk.chkRedecl(ctx, name, pos) {
+								fld := types.NewField(pos, pkg, name, typ, true)
+								flds = append(flds, fld)
+								tags = append(tags, "")
+							}
 						}
+					} else { // embed work classes for project class
+						flds = proj.embed(flds, p)
 					}
 				}
 				rec := ctx.recorder()
diff --git a/cl/expr.go b/cl/expr.go
index 6b351729..15b3ed92 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -585,14 +585,15 @@ func identVal(ctx *blockCtx, x *ast.Ident, flags int, v types.Object, alias bool
 }
 
 type fnType struct {
-	next      *fnType
-	params    *types.Tuple
-	sig       *types.Signature
-	base      int
-	size      int
-	variadic  bool
-	typetype  bool
-	typeparam bool
+	next         *fnType
+	params       *types.Tuple
+	sig          *types.Signature
+	base         int
+	size         int
+	variadic     bool
+	typetype     bool
+	typeparam    bool
+	typeAsParams bool
 }
 
 func (p *fnType) arg(i int, ellipsis bool) types.Type {
@@ -609,9 +610,10 @@ func (p *fnType) arg(i int, ellipsis bool) types.Type {
 	return nil
 }
 
-func (p *fnType) init(base int, t *types.Signature) {
+func (p *fnType) init(base int, t *types.Signature, typeAsParams bool) {
 	p.base = base
 	p.sig = t
+	p.typeAsParams = typeAsParams
 	p.params, p.variadic, p.typeparam = t.Params(), t.Variadic(), t.TypeParams() != nil
 	p.size = p.params.Len()
 	if p.variadic {
@@ -633,24 +635,27 @@ func (p *fnType) load(fnt types.Type) {
 		typ, objs := gogen.CheckSigFuncExObjects(v)
 		switch typ.(type) {
 		case *gogen.TyOverloadFunc, *gogen.TyOverloadMethod:
-			p.initFuncs(0, objs)
+			p.initFuncs(0, objs, false)
 			return
 		case *gogen.TyTemplateRecvMethod:
-			p.initFuncs(1, objs)
+			p.initFuncs(1, objs, false)
+			return
+		case *gogen.TyTypeAsParams:
+			p.initFuncs(1, objs, true)
 			return
 		}
-		p.init(0, v)
+		p.init(0, v, false)
 	}
 }
 
-func (p *fnType) initFuncs(base int, funcs []types.Object) {
+func (p *fnType) initFuncs(base int, funcs []types.Object, typeAsParams bool) {
 	for i, obj := range funcs {
 		if sig, ok := obj.Type().(*types.Signature); ok {
 			if i == 0 {
-				p.init(base, sig)
+				p.init(base, sig, typeAsParams)
 			} else {
 				fn := &fnType{}
-				fn.init(base, sig)
+				fn.init(base, sig, typeAsParams)
 				p.next = fn
 				p = p.next
 			}
@@ -766,8 +771,34 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 			err = ctx.recoverErr(r, v)
 		}
 	}()
+
+	vargs := v.Args
+	if fn.typeAsParams && fn.typeparam {
+		n := fn.sig.TypeParams().Len()
+		for i := 0; i < n; i++ {
+			compileExpr(ctx, vargs[i])
+		}
+		args := ctx.cb.InternalStack().GetArgs(n)
+		var targs []types.Type
+		for i, arg := range args {
+			typ := arg.Type
+			t, ok := typ.(*gogen.TypeType)
+			if !ok {
+				return ctx.newCodeErrorf(vargs[i].Pos(), "%v not type", ctx.LoadExpr(vargs[i]))
+			}
+			targs = append(targs, t.Type())
+		}
+		ret, err := types.Instantiate(nil, fn.sig, targs, true)
+		if err != nil {
+			return ctx.newCodeError(v.Pos(), err.Error())
+		}
+		fn.init(1, ret.(*types.Signature), false)
+		vargs = vargs[n:]
+	}
+
 	var needInferFunc bool
-	for i, arg := range v.Args {
+	for i, arg := range vargs {
+		t := fn.arg(i, ellipsis)
 		switch expr := arg.(type) {
 		case *ast.LambdaExpr:
 			if fn.typeparam {
@@ -775,7 +806,7 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 				compileIdent(ctx, ast.NewIdent("nil"), 0)
 				continue
 			}
-			sig, e := checkLambdaFuncType(ctx, expr, fn.arg(i, ellipsis), clLambaArgument, v.Fun)
+			sig, e := checkLambdaFuncType(ctx, expr, t, clLambaArgument, v.Fun)
 			if e != nil {
 				return e
 			}
@@ -788,7 +819,7 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 				compileIdent(ctx, ast.NewIdent("nil"), 0)
 				continue
 			}
-			sig, e := checkLambdaFuncType(ctx, expr, fn.arg(i, ellipsis), clLambaArgument, v.Fun)
+			sig, e := checkLambdaFuncType(ctx, expr, t, clLambaArgument, v.Fun)
 			if e != nil {
 				return e
 			}
@@ -796,11 +827,10 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 				return
 			}
 		case *ast.CompositeLit:
-			if err = compileCompositeLitEx(ctx, expr, fn.arg(i, ellipsis), true); err != nil {
+			if err = compileCompositeLitEx(ctx, expr, t, true); err != nil {
 				return
 			}
 		case *ast.SliceLit:
-			t := fn.arg(i, ellipsis)
 			switch t.(type) {
 			case *types.Slice:
 			case *types.Named:
@@ -821,9 +851,15 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 				return
 			}
 		case *ast.NumberUnitLit:
-			compileNumberUnitLit(ctx, expr, fn.arg(i, ellipsis))
+			compileNumberUnitLit(ctx, expr, t)
 		default:
 			compileExpr(ctx, arg)
+			if sigParamLen(t) == 0 {
+				cb := ctx.cb
+				if nonClosure(cb.Get(-1).Type) {
+					cb.ConvertToClosure()
+				}
+			}
 		}
 	}
 	if needInferFunc {
@@ -833,7 +869,7 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 			return err
 		}
 		next := &fnType{}
-		next.init(fn.base, typ.(*types.Signature))
+		next.init(fn.base, typ.(*types.Signature), false)
 		next.next = fn.next
 		fn.next = next
 		return errCallNext
@@ -876,6 +912,34 @@ retry:
 	return nil, ctx.newCodeErrorf(lambda.Pos(), "cannot use lambda literal as type %v in %v%v", ftyp, flag, to)
 }
 
+func sigParamLen(typ types.Type) int {
+retry:
+	switch t := typ.(type) {
+	case *types.Signature:
+		return t.Params().Len()
+	case *types.Named:
+		typ = t.Underlying()
+		goto retry
+	}
+	return -1
+}
+
+func nonClosure(typ types.Type) bool {
+retry:
+	switch t := typ.(type) {
+	case *types.Signature:
+		return false
+	case *types.Basic:
+		if t.Kind() == types.UntypedNil {
+			return false
+		}
+	case *types.Named:
+		typ = t.Underlying()
+		goto retry
+	}
+	return true
+}
+
 func compileLambda(ctx *blockCtx, lambda ast.Expr, sig *types.Signature) {
 	switch expr := lambda.(type) {
 	case *ast.LambdaExpr2:
@@ -1082,8 +1146,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 +1206,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/overload/bar/bar.go b/cl/internal/overload/bar/bar.go
index b675663b..3121019f 100644
--- a/cl/internal/overload/bar/bar.go
+++ b/cl/internal/overload/bar/bar.go
@@ -31,3 +31,16 @@ func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
 func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 	return new(Var__1[T])
 }
+
+type Player struct {
+}
+
+func Gopt_Player_Gopx_OnCmd__0[T any](p *Player, handler func(cmd T) error) {
+	var t T
+	handler(t)
+}
+
+func Gopt_Player_Gopx_OnCmd__1[T1 ~int, T2 any](p *Player, n T1, handler func(n T1, cmd T2) error) {
+	var t T2
+	handler(n, t)
+}
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/cmd/chore/gopbuiltingen/builtingen.gox b/cmd/chore/gopbuiltingen/builtingen.gox
index 13935193..be26c170 100644
--- a/cmd/chore/gopbuiltingen/builtingen.gox
+++ b/cmd/chore/gopbuiltingen/builtingen.gox
@@ -27,7 +27,7 @@ func gen() []byte {
 	f.Decls <- &ast.GenDecl{
 		Tok: token.IMPORT,
 		Specs: []ast.Spec{
-			importSpec("gop/builtin/iox"),
+			importSpec("github.com/qiniu/x/gop/osx"),
 			importSpec("io"),
 			importSpec("os"),
 			importSpec("reflect"),
@@ -40,12 +40,12 @@ func gen() []byte {
 }
 
 func genDecls(f *ast.File) {
-	for built <- Builtins {
+	for built in Builtins {
 		f.Decls <- built.genAST()
 	}
-	for t <- Types {
+	for t in Types {
 		mthds := t.Methods
-		for m <- mthds {
+		for m in mthds {
 			f.Decls <- m.genMethodAST(mthds)
 		}
 	}
@@ -54,9 +54,9 @@ func genDecls(f *ast.File) {
 func initBuiltinTIs(fn gopq.NodeSet, f *gopast.File) (tistr *builtinTI) {
 	ti := fn.body.any.assignStmt.rhs(0).x.compositeLit("BuiltinTI")
 	methods := ti.elt("methods").cache
-	for method <- methods {
+	for method in methods {
 		aTI := &builtinTI{}
-		for item <- method.elt {
+		for item in method.elt {
 			mthd := item.elt(0).unquotedString!
 			fn := item.elt(1).one
 			if ref := fn.callExpr.one; ref.ok {
@@ -84,9 +84,9 @@ func initBuiltinTIs(fn gopq.NodeSet, f *gopast.File) (tistr *builtinTI) {
 
 func newBuiltinDefault(fn gopq.NodeSet, tistr *builtinTI) {
 	methods := fn.body.any.exprStmt.x.callExpr("ti.AddMethods").cache
-	for method <- methods {
+	for method in methods {
 		aTI := &builtinTI{}
-		for arg <- method.varg(0).x {
+		for arg in method.varg(0).x {
 			mthd := arg.elt("Name").unquotedString!
 			ref := arg.elt("Fn").callExpr.one
 			pkg := ref.fun.x.ident!
@@ -106,7 +106,7 @@ func newBuiltinDefault(fn gopq.NodeSet, tistr *builtinTI) {
 func initBuiltin(fn gopq.NodeSet) {
 	stmt := fn.body.any.exprStmt.x.cache
 	item := stmt.callExpr("scope.Insert").arg(0).cache
-	for call <- item.callExpr("gogen.NewOverloadFunc") {
+	for call in item.callExpr("gogen.NewOverloadFunc") {
 		built := call.arg(2).unquotedString!
 		if built != "newRange" { // hide builtin `newRange`
 			ref := call.arg(3).callExpr.one
@@ -115,10 +115,10 @@ func initBuiltin(fn gopq.NodeSet) {
 			Builtins <- builtin{built, {pkg, name, nil}}
 		}
 	}
-	for call <- stmt.callExpr("initBuiltinFns") {
+	for call in stmt.callExpr("initBuiltinFns") {
 		pkg := call.arg(2).ident!
 		builtins := call.arg(3).unquotedStringElts!
-		for built <- builtins {
+		for built in builtins {
 			Builtins <- builtin{built, {pkg, built.capitalize, nil}}
 		}
 	}
diff --git a/cmd/chore/gopbuiltingen/helper.gop b/cmd/chore/gopbuiltingen/helper.gop
index 6278fb15..b32a92c1 100644
--- a/cmd/chore/gopbuiltingen/helper.gop
+++ b/cmd/chore/gopbuiltingen/helper.gop
@@ -29,7 +29,7 @@ func rmExargs(list []*ast.Field, ex *exargs) []*ast.Field {
 	}
 	n := len(list)
 	ret := make([]*ast.Field, n)
-	for i, f <- list {
+	for i, f in list {
 		ret[i] = f
 	}
 	exargs := ex.N
@@ -51,7 +51,7 @@ func toParams(params *ast.FieldList, at string) *ast.FieldList {
 		return nil
 	}
 	list := make([]*ast.Field, len(params.List))
-	for i, p <- params.List {
+	for i, p in params.List {
 		typ := p.Type
 		switch t := typ.(type) {
 		case *ast.Ident:
diff --git a/cmd/chore/gopbuiltingen/reference.gox b/cmd/chore/gopbuiltingen/reference.gox
index c29524f8..cb12fb36 100644
--- a/cmd/chore/gopbuiltingen/reference.gox
+++ b/cmd/chore/gopbuiltingen/reference.gox
@@ -58,9 +58,10 @@ var (
 		"strconv": "${goroot}/src/strconv",
 		"strings": "${goroot}/src/strings",
 
-		"buil":        "${root}/builtin",
-		"iox":         "${root}/builtin/iox",
-		"stringslice": "${root}/builtin/stringslice",
+		"buil":        "${root}/../x/gop",
+		"iox":         "${root}/../x/gop/osx",
+		"osx":         "${root}/../x/gop/osx",
+		"stringslice": "${root}/../x/stringslice",
 		"strx":        "${root}/../x/stringutil",
 	}
 )
@@ -75,8 +76,8 @@ func getAST() *ast.FuncDecl {
 }
 
 func .funcDecl(pkg *ast.Package, name string) *ast.FuncDecl {
-	for file <- pkg.Files {
-		for decl <- file.Decls {
+	for file in pkg.Files {
+		for decl in file.Decls {
 			if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == name {
 				return fn
 			}
@@ -95,7 +96,7 @@ func .pkgOf(pkgPath string) *ast.Package {
 		panic("unknown package: ${pkgPath}")
 	}
 
-	for name, pkg <- parser.parseDir(fset, dir, nil, parser.ParseComments)! {
+	for name, pkg in parser.parseDir(fset, dir, nil, parser.ParseComments)! {
 		if name.hasSuffix("_test") {
 			continue
 		}
diff --git a/cmd/internal/mod/download.go b/cmd/internal/mod/download.go
index 676ec51c..6448cc9b 100644
--- a/cmd/internal/mod/download.go
+++ b/cmd/internal/mod/download.go
@@ -22,11 +22,11 @@ import (
 )
 
 // gop mod download
-var cmdDownload = &base.Command{
+var CmdDownload = &base.Command{
 	UsageLine: "gop mod download [-x -json] [modules]",
 	Short:     "download modules to local cache",
 }
 
 func init() {
-	cmdDownload.Run = gopget.Cmd.Run
+	CmdDownload.Run = gopget.Cmd.Run
 }
diff --git a/cmd/internal/mod/init.go b/cmd/internal/mod/init.go
index a709fbc0..cb142966 100644
--- a/cmd/internal/mod/init.go
+++ b/cmd/internal/mod/init.go
@@ -29,19 +29,19 @@ import (
 )
 
 // gop mod init
-var cmdInit = &base.Command{
+var CmdInit = &base.Command{
 	UsageLine: "gop mod init [-llgo -tinygo] module-path",
 	Short:     "initialize new module in current directory",
 }
 
 var (
-	flagInit   = &cmdInit.Flag
+	flagInit   = &CmdInit.Flag
 	flagLLGo   = flagInit.Bool("llgo", false, "use llgo as the compiler")
 	flagTinyGo = flagInit.Bool("tinygo", false, "use tinygo as the compiler")
 )
 
 func init() {
-	cmdInit.Run = runInit
+	CmdInit.Run = runInit
 }
 
 func runInit(cmd *base.Command, args []string) {
diff --git a/cmd/internal/mod/mod.go b/cmd/internal/mod/mod.go
index cc6fd3e8..c4732848 100644
--- a/cmd/internal/mod/mod.go
+++ b/cmd/internal/mod/mod.go
@@ -28,9 +28,9 @@ var Cmd = &base.Command{
 	Short:     "Module maintenance",
 
 	Commands: []*base.Command{
-		cmdInit,
-		cmdDownload,
-		cmdTidy,
+		CmdInit,
+		CmdDownload,
+		CmdTidy,
 	},
 }
 
diff --git a/cmd/internal/mod/tidy.go b/cmd/internal/mod/tidy.go
index a40ce253..cbe8f3ee 100644
--- a/cmd/internal/mod/tidy.go
+++ b/cmd/internal/mod/tidy.go
@@ -26,13 +26,13 @@ import (
 )
 
 // gop mod tidy
-var cmdTidy = &base.Command{
+var CmdTidy = &base.Command{
 	UsageLine: "gop mod tidy [-e -v]",
 	Short:     "add missing and remove unused modules",
 }
 
 func init() {
-	cmdTidy.Run = runTidy
+	CmdTidy.Run = runTidy
 }
 
 func runTidy(cmd *base.Command, args []string) {
diff --git a/cmd/xgo/bug_cmd.gox b/cmd/xgo/bug_cmd.gox
new file mode 100644
index 00000000..e670a5eb
--- /dev/null
+++ b/cmd/xgo/bug_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/bug"
+)
+
+short "Start a bug report"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/build_cmd.gox b/cmd/xgo/build_cmd.gox
new file mode 100644
index 00000000..abc99b3b
--- /dev/null
+++ b/cmd/xgo/build_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/build"
+)
+
+short "Build Go+ files"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/clean_cmd.gox b/cmd/xgo/clean_cmd.gox
new file mode 100644
index 00000000..308bfed3
--- /dev/null
+++ b/cmd/xgo/clean_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/clean"
+)
+
+short "Clean all Go+ auto generated files"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/doc_cmd.gox b/cmd/xgo/doc_cmd.gox
new file mode 100644
index 00000000..5ec0c871
--- /dev/null
+++ b/cmd/xgo/doc_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/doc"
+)
+
+short "Show documentation for package or symbol"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/env_cmd.gox b/cmd/xgo/env_cmd.gox
new file mode 100644
index 00000000..9131b3e6
--- /dev/null
+++ b/cmd/xgo/env_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/env"
+)
+
+short "Prints Go+ environment information"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/fmt_cmd.gox b/cmd/xgo/fmt_cmd.gox
new file mode 100644
index 00000000..7d6fc49e
--- /dev/null
+++ b/cmd/xgo/fmt_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/gopfmt"
+)
+
+short "Format Go+ packages"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/get_cmd.gox b/cmd/xgo/get_cmd.gox
new file mode 100644
index 00000000..cd7ed6e8
--- /dev/null
+++ b/cmd/xgo/get_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/gopget"
+)
+
+short "Add dependencies to current module and install them"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/go_cmd.gox b/cmd/xgo/go_cmd.gox
new file mode 100644
index 00000000..0767d7ed
--- /dev/null
+++ b/cmd/xgo/go_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/gengo"
+)
+
+short "Convert Go+ code into Go code"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/gop_autogen.go b/cmd/xgo/gop_autogen.go
new file mode 100644
index 00000000..2bfc9675
--- /dev/null
+++ b/cmd/xgo/gop_autogen.go
@@ -0,0 +1,426 @@
+// Code generated by gop (Go+); DO NOT EDIT.
+
+package main
+
+import (
+	fmt1 "fmt"
+	"github.com/goplus/cobra/xcmd"
+	bug1 "github.com/goplus/gop/cmd/internal/bug"
+	build1 "github.com/goplus/gop/cmd/internal/build"
+	clean1 "github.com/goplus/gop/cmd/internal/clean"
+	doc1 "github.com/goplus/gop/cmd/internal/doc"
+	env1 "github.com/goplus/gop/cmd/internal/env"
+	"github.com/goplus/gop/cmd/internal/gengo"
+	"github.com/goplus/gop/cmd/internal/gopfmt"
+	"github.com/goplus/gop/cmd/internal/gopget"
+	install1 "github.com/goplus/gop/cmd/internal/install"
+	mod1 "github.com/goplus/gop/cmd/internal/mod"
+	run1 "github.com/goplus/gop/cmd/internal/run"
+	serve1 "github.com/goplus/gop/cmd/internal/serve"
+	test1 "github.com/goplus/gop/cmd/internal/test"
+	watch1 "github.com/goplus/gop/cmd/internal/watch"
+	env2 "github.com/goplus/gop/env"
+	"github.com/qiniu/x/stringutil"
+	"runtime"
+)
+
+const _ = true
+
+type bug struct {
+	xcmd.Command
+	*App
+}
+type build struct {
+	xcmd.Command
+	*App
+}
+type clean struct {
+	xcmd.Command
+	*App
+}
+type doc struct {
+	xcmd.Command
+	*App
+}
+type env struct {
+	xcmd.Command
+	*App
+}
+type fmt struct {
+	xcmd.Command
+	*App
+}
+type get struct {
+	xcmd.Command
+	*App
+}
+type _go struct {
+	xcmd.Command
+	*App
+}
+type install struct {
+	xcmd.Command
+	*App
+}
+type mod struct {
+	xcmd.Command
+	*App
+}
+type mod_download struct {
+	xcmd.Command
+	*App
+}
+type mod_init struct {
+	xcmd.Command
+	*App
+}
+type mod_tidy struct {
+	xcmd.Command
+	*App
+}
+type run struct {
+	xcmd.Command
+	*App
+}
+type serve struct {
+	xcmd.Command
+	*App
+}
+type test struct {
+	xcmd.Command
+	*App
+}
+type version struct {
+	xcmd.Command
+	*App
+}
+type watch struct {
+	xcmd.Command
+	*App
+}
+type App struct {
+	xcmd.App
+}
+
+func (this *App) Main() {
+	_gop_obj0 := &bug{App: this}
+	_gop_obj1 := &build{App: this}
+	_gop_obj2 := &clean{App: this}
+	_gop_obj3 := &doc{App: this}
+	_gop_obj4 := &env{App: this}
+	_gop_obj5 := &fmt{App: this}
+	_gop_obj6 := &get{App: this}
+	_gop_obj7 := &_go{App: this}
+	_gop_obj8 := &install{App: this}
+	_gop_obj9 := &mod{App: this}
+	_gop_obj10 := &mod_download{App: this}
+	_gop_obj11 := &mod_init{App: this}
+	_gop_obj12 := &mod_tidy{App: this}
+	_gop_obj13 := &run{App: this}
+	_gop_obj14 := &serve{App: this}
+	_gop_obj15 := &test{App: this}
+	_gop_obj16 := &version{App: this}
+	_gop_obj17 := &watch{App: this}
+	xcmd.Gopt_App_Main(this, _gop_obj0, _gop_obj1, _gop_obj2, _gop_obj3, _gop_obj4, _gop_obj5, _gop_obj6, _gop_obj7, _gop_obj8, _gop_obj9, _gop_obj10, _gop_obj11, _gop_obj12, _gop_obj13, _gop_obj14, _gop_obj15, _gop_obj16, _gop_obj17)
+}
+//line cmd/xgo/bug_cmd.gox:20
+func (this *bug) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/bug_cmd.gox:20:1
+	this.Short("Start a bug report")
+//line cmd/xgo/bug_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/bug_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/bug_cmd.gox:25:1
+		bug1.Cmd.Run(bug1.Cmd, args)
+	})
+}
+func (this *bug) Classfname() string {
+	return "bug"
+}
+//line cmd/xgo/build_cmd.gox:20
+func (this *build) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/build_cmd.gox:20:1
+	this.Short("Build Go+ files")
+//line cmd/xgo/build_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/build_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/build_cmd.gox:25:1
+		build1.Cmd.Run(build1.Cmd, args)
+	})
+}
+func (this *build) Classfname() string {
+	return "build"
+}
+//line cmd/xgo/clean_cmd.gox:20
+func (this *clean) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/clean_cmd.gox:20:1
+	this.Short("Clean all Go+ auto generated files")
+//line cmd/xgo/clean_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/clean_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/clean_cmd.gox:25:1
+		clean1.Cmd.Run(clean1.Cmd, args)
+	})
+}
+func (this *clean) Classfname() string {
+	return "clean"
+}
+//line cmd/xgo/doc_cmd.gox:20
+func (this *doc) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/doc_cmd.gox:20:1
+	this.Short("Show documentation for package or symbol")
+//line cmd/xgo/doc_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/doc_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/doc_cmd.gox:25:1
+		doc1.Cmd.Run(doc1.Cmd, args)
+	})
+}
+func (this *doc) Classfname() string {
+	return "doc"
+}
+//line cmd/xgo/env_cmd.gox:20
+func (this *env) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/env_cmd.gox:20:1
+	this.Short("Prints Go+ environment information")
+//line cmd/xgo/env_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/env_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/env_cmd.gox:25:1
+		env1.Cmd.Run(env1.Cmd, args)
+	})
+}
+func (this *env) Classfname() string {
+	return "env"
+}
+//line cmd/xgo/fmt_cmd.gox:20
+func (this *fmt) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/fmt_cmd.gox:20:1
+	this.Short("Format Go+ packages")
+//line cmd/xgo/fmt_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/fmt_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/fmt_cmd.gox:25:1
+		gopfmt.Cmd.Run(gopfmt.Cmd, args)
+	})
+}
+func (this *fmt) Classfname() string {
+	return "fmt"
+}
+//line cmd/xgo/get_cmd.gox:20
+func (this *get) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/get_cmd.gox:20:1
+	this.Short("Add dependencies to current module and install them")
+//line cmd/xgo/get_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/get_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/get_cmd.gox:25:1
+		gopget.Cmd.Run(gopget.Cmd, args)
+	})
+}
+func (this *get) Classfname() string {
+	return "get"
+}
+//line cmd/xgo/go_cmd.gox:20
+func (this *_go) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/go_cmd.gox:20:1
+	this.Short("Convert Go+ code into Go code")
+//line cmd/xgo/go_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/go_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/go_cmd.gox:25:1
+		gengo.Cmd.Run(gengo.Cmd, args)
+	})
+}
+func (this *_go) Classfname() string {
+	return "go"
+}
+//line cmd/xgo/install_cmd.gox:20
+func (this *install) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/install_cmd.gox:20:1
+	this.Short("Build Go+ files and install target to GOBIN")
+//line cmd/xgo/install_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/install_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/install_cmd.gox:25:1
+		install1.Cmd.Run(install1.Cmd, args)
+	})
+}
+func (this *install) Classfname() string {
+	return "install"
+}
+//line cmd/xgo/mod_cmd.gox:20
+func (this *mod) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/mod_cmd.gox:20:1
+	this.Short("Module maintenance")
+//line cmd/xgo/mod_cmd.gox:22:1
+	this.Run__0(func() {
+//line cmd/xgo/mod_cmd.gox:23:1
+		this.Help()
+	})
+}
+func (this *mod) Classfname() string {
+	return "mod"
+}
+//line cmd/xgo/mod_download_cmd.gox:20
+func (this *mod_download) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/mod_download_cmd.gox:20:1
+	this.Short("download modules to local cache")
+//line cmd/xgo/mod_download_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/mod_download_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/mod_download_cmd.gox:25:1
+		if len(args) < 1 {
+//line cmd/xgo/mod_download_cmd.gox:26:1
+			this.Help()
+//line cmd/xgo/mod_download_cmd.gox:27:1
+			return
+		}
+//line cmd/xgo/mod_download_cmd.gox:29:1
+		mod1.CmdDownload.Run(mod1.CmdDownload, args)
+	})
+}
+func (this *mod_download) Classfname() string {
+	return "mod_download"
+}
+//line cmd/xgo/mod_init_cmd.gox:20
+func (this *mod_init) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/mod_init_cmd.gox:20:1
+	this.Short("initialize new module in current directory")
+//line cmd/xgo/mod_init_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/mod_init_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/mod_init_cmd.gox:25:1
+		if len(args) < 1 {
+//line cmd/xgo/mod_init_cmd.gox:26:1
+			this.Help()
+//line cmd/xgo/mod_init_cmd.gox:27:1
+			return
+		}
+//line cmd/xgo/mod_init_cmd.gox:29:1
+		mod1.CmdInit.Run(mod1.CmdInit, args)
+	})
+}
+func (this *mod_init) Classfname() string {
+	return "mod_init"
+}
+//line cmd/xgo/mod_tidy_cmd.gox:20
+func (this *mod_tidy) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/mod_tidy_cmd.gox:20:1
+	this.Short("add missing and remove unused modules")
+//line cmd/xgo/mod_tidy_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/mod_tidy_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/mod_tidy_cmd.gox:25:1
+		mod1.CmdTidy.Run(mod1.CmdTidy, args)
+	})
+}
+func (this *mod_tidy) Classfname() string {
+	return "mod_tidy"
+}
+//line cmd/xgo/run_cmd.gox:20
+func (this *run) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/run_cmd.gox:20:1
+	this.Short("Compile and run a Go+ program")
+//line cmd/xgo/run_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/run_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/run_cmd.gox:25:1
+		run1.Cmd.Run(run1.Cmd, args)
+	})
+}
+func (this *run) Classfname() string {
+	return "run"
+}
+//line cmd/xgo/serve_cmd.gox:20
+func (this *serve) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/serve_cmd.gox:20:1
+	this.Short("Serve as a Go+ LangServer")
+//line cmd/xgo/serve_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/serve_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/serve_cmd.gox:25:1
+		serve1.Cmd.Run(serve1.Cmd, args)
+	})
+}
+func (this *serve) Classfname() string {
+	return "serve"
+}
+//line cmd/xgo/test_cmd.gox:20
+func (this *test) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/test_cmd.gox:20:1
+	this.Short("Test Go+ packages")
+//line cmd/xgo/test_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/test_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/test_cmd.gox:25:1
+		test1.Cmd.Run(test1.Cmd, args)
+	})
+}
+func (this *test) Classfname() string {
+	return "test"
+}
+//line cmd/xgo/version_cmd.gox:21
+func (this *version) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/version_cmd.gox:21:1
+	this.Short("Print Go+ version")
+//line cmd/xgo/version_cmd.gox:23:1
+	this.Run__0(func() {
+//line cmd/xgo/version_cmd.gox:24:1
+		fmt1.Println(stringutil.Concat("xgo ", env2.Version(), " ", runtime.GOOS, "/", runtime.GOARCH))
+	})
+}
+func (this *version) Classfname() string {
+	return "version"
+}
+//line cmd/xgo/watch_cmd.gox:20
+func (this *watch) Main(_gop_arg0 string) {
+	this.Command.Main(_gop_arg0)
+//line cmd/xgo/watch_cmd.gox:20:1
+	this.Short("Monitor code changes in a Go+ workspace to generate Go files")
+//line cmd/xgo/watch_cmd.gox:22:1
+	this.FlagOff()
+//line cmd/xgo/watch_cmd.gox:24:1
+	this.Run__1(func(args []string) {
+//line cmd/xgo/watch_cmd.gox:25:1
+		watch1.Cmd.Run(watch1.Cmd, args)
+	})
+}
+func (this *watch) Classfname() string {
+	return "watch"
+}
+func main() {
+	new(App).Main()
+}
diff --git a/cmd/xgo/install_cmd.gox b/cmd/xgo/install_cmd.gox
new file mode 100644
index 00000000..3c0541ef
--- /dev/null
+++ b/cmd/xgo/install_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/install"
+)
+
+short "Build Go+ files and install target to GOBIN"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/mod_cmd.gox b/cmd/xgo/mod_cmd.gox
new file mode 100644
index 00000000..40005530
--- /dev/null
+++ b/cmd/xgo/mod_cmd.gox
@@ -0,0 +1,24 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/mod"
+)
+
+short "Module maintenance"
+
+run => {
+	help
+}
diff --git a/cmd/xgo/mod_download_cmd.gox b/cmd/xgo/mod_download_cmd.gox
new file mode 100644
index 00000000..6ccd1805
--- /dev/null
+++ b/cmd/xgo/mod_download_cmd.gox
@@ -0,0 +1,30 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/mod"
+)
+
+short "download modules to local cache"
+
+flagOff
+
+run args => {
+	if args.len < 1 {
+		help
+		return
+	}
+	self.CmdDownload.Run self.CmdDownload, args
+}
diff --git a/cmd/xgo/mod_init_cmd.gox b/cmd/xgo/mod_init_cmd.gox
new file mode 100644
index 00000000..ef41795c
--- /dev/null
+++ b/cmd/xgo/mod_init_cmd.gox
@@ -0,0 +1,30 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/mod"
+)
+
+short "initialize new module in current directory"
+
+flagOff
+
+run args => {
+	if args.len < 1 {
+		help
+		return
+	}
+	self.CmdInit.Run self.CmdInit, args
+}
diff --git a/cmd/xgo/mod_tidy_cmd.gox b/cmd/xgo/mod_tidy_cmd.gox
new file mode 100644
index 00000000..497cb45e
--- /dev/null
+++ b/cmd/xgo/mod_tidy_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/mod"
+)
+
+short "add missing and remove unused modules"
+
+flagOff
+
+run args => {
+	self.CmdTidy.Run self.CmdTidy, args
+}
diff --git a/cmd/xgo/run_cmd.gox b/cmd/xgo/run_cmd.gox
new file mode 100644
index 00000000..c50bb82c
--- /dev/null
+++ b/cmd/xgo/run_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/run"
+)
+
+short "Compile and run a Go+ program"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/serve_cmd.gox b/cmd/xgo/serve_cmd.gox
new file mode 100644
index 00000000..2e3c2be1
--- /dev/null
+++ b/cmd/xgo/serve_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/serve"
+)
+
+short "Serve as a Go+ LangServer"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/cmd/xgo/version_cmd.gox b/cmd/xgo/version_cmd.gox
new file mode 100644
index 00000000..0e7d61ff
--- /dev/null
+++ b/cmd/xgo/version_cmd.gox
@@ -0,0 +1,25 @@
+/*
+ * 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 (
+	self "gop/env"
+	"runtime"
+)
+
+short "Print Go+ version"
+
+run => {
+	echo "xgo ${self.version} ${runtime.GOOS}/${runtime.GOARCH}"
+}
diff --git a/cmd/xgo/watch_cmd.gox b/cmd/xgo/watch_cmd.gox
new file mode 100644
index 00000000..532107f1
--- /dev/null
+++ b/cmd/xgo/watch_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/watch"
+)
+
+short "Monitor code changes in a Go+ workspace to generate Go files"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/doc/classfile.md b/doc/classfile.md
index c46f0049..33079bd2 100644
--- a/doc/classfile.md
+++ b/doc/classfile.md
@@ -6,7 +6,7 @@ One language can change the world.
 Go+ is a "DSL" for all domains.
 ```
 
-Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` is as important to Go+ as `interface` is to Go.
+Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` (and `class framework`) is as important to Go+ as `interface` is to Go.
 
 In the design philosophy of Go+, we do not recommend `DSL` (Domain Specific Language). But `SDF` (Specific Domain Friendliness) is very important. The Go+ philosophy about `SDF` is:
 
@@ -15,14 +15,14 @@ Don't define a language for specific domain.
 Abstract domain knowledge for it.
 ```
 
-Go+ introduces `classfile` to abstract domain knowledge.
+Go+ introduces `classfile` and `class framework` to abstract domain knowledge.
 
 * STEM Education: [spx: A Go+ 2D Game Engine](https://github.com/goplus/spx)
 * Web Programming: [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap)
 * Web Programming: [yaptest: HTTP Test Framework](https://github.com/goplus/yap#yaptest-http-test-framework)
 * Web Programming: [ydb: Database Framework](https://github.com/goplus/yap#ydb-database-framework)
 * DevOps: [gsh: Go+ DevOps Tools](https://github.com/qiniu/x/tree/main/gsh)
-* Unit Test: [classfile: Unit Test](#classfile-unit-test)
+* Unit Test: [classfile: Unit Test](#class-framework-unit-test)
 * Mechanism: [What's Classfile](#whats-classfile)
 
 Sound a bit abstract? Let's take web programming as an example. First let us create a file named [get.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_hello/get.yap) with the following content:
@@ -124,20 +124,22 @@ func Area() int {
 
 Defining variables and defining functions are all familiar to them while learning sequential programming. They can define new types using syntax they already know by heart. This will be valuable in getting a wider community to learn Go+.
 
+### What's class framework
+
 Of course, this is not enough to make classfiles an exciting feature. What's more important is its ability to abstract domain knowledge. It is accomplished by defining `base class` for a class and defining `relationships between multiple classes`.
 
-What is a `classfile`? Usually it consists of a `project class` and multiple `work classes`. The classfile not only specifies the `base class` of all `project class` and `work classes`, but also organizes all these classes together by the base class of project class. There can be no work classes, that is, the entire classfile consists of only one project class.
+What is a `class framework`? Usually it consists of a `project class` and multiple `work classes`. The class framework not only specifies the `base class` of all `project class` and `work classes`, but also organizes all these classes together by the base class of project class. There can be no work classes, that is, the entire classfile consists of only one project class.
 
-This is a bit abstract. Let's take the [2D Game Engine spx](https://github.com/goplus/spx) as an example. The base class of project class of `spx classfile` is called `Game`. The base class of work class is called `Sprite`. Obviously, there will only be one Game instance in a game, but there are many types of sprites, so many types of work classes are needed, but they all have the same base class called `Sprite`. Go+'s classfile allows you to specify different base classes for different work classes. Although this is rare, it can be done.
+This is a bit abstract. Let's take the [2D Game Engine spx](https://github.com/goplus/spx) as an example. The base class of project class of `spx class framework` is called `Game`. The base class of work class is called `Sprite`. Obviously, there will only be one Game instance in a game, but there are many types of sprites, so many types of work classes are needed, but they all have the same base class called `Sprite`. Go+'s class framework allows you to specify different base classes for different work classes. Although this is rare, it can be done.
 
-How does Go+ identify various class files of a classfile? by its filename. By convention, if we define a classfile called `foo`, then its project class is usually called `main_foo.gox`, and the work class is usually called `xxx_foo.gox`. If this classfile does not have a work class, then the project class only needs to ensure that the suffix is `_foo.gox`, and the class name can be freely chosen.
+How does Go+ identify various class files of a class framework? by its filename. By convention, if we define a class framework called `foo`, then its project class is usually called `main_foo.gox`, and the work class is usually called `xxx_foo.gox`. If this class framework does not have a work class, then the project class only needs to ensure that the suffix is `_foo.gox`, and the class name can be freely chosen.
 
-The earliest version of Go+ allows classfiles to be identified through custom file extensions. For example, the project class of the `spx classfile` is called `main.spx`, and the work class is called `xxx.spx`. Although this ability to customize extensions is still retained for now, we do not recommend its use and there is no guarantee that it will continue to be available in the future.
+The earliest version of Go+ allows classfiles to be identified through custom file extensions. For example, the project class of the `spx class framework` is called `main.spx`, and the work class is called `xxx.spx`. Although this ability to customize extensions is still retained for now, we do not recommend its use and there is no guarantee that it will continue to be available in the future.
 
 
-### classfile: Unit Test
+### class framework: Unit Test
 
-Go+ has a built-in classfile to simplify unit testing. This classfile has the file suffix `_test.gox`.
+Go+ has a built-in class framework to simplify unit testing. This class framework has the file suffix `_test.gox`.
 
 Suppose you have a function named `foo`:
 
@@ -174,7 +176,7 @@ If you want to run a subtest case, use `t.run`.
 
 ### yap: Yet Another Go/Go+ HTTP Web Framework
 
-This classfile has the file suffix `.yap`. See [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) for more details.
+This class framework has the file suffix `.yap`. See [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) for more details.
 
 #### Router and Parameters
 
@@ -237,7 +239,7 @@ run ":8080"
 
 ### yaptest: HTTP Test Framework
 
-This classfile has the file suffix `_ytest.gox`.
+This class framework has the file suffix `_ytest.gox`.
 
 Suppose we have a web server ([foo/get_p_#id.yap](https://github.com/goplus/yap/blob/main/ytest/demo/foo/get_p_%23id.yap)):
 
@@ -282,7 +284,7 @@ For more details, see [yaptest - Go+ HTTP Test Framework](https://github.com/gop
 
 ### spx: A Go+ 2D Game Engine for STEM education
 
-This classfile has the file suffix `.spx`. It is the earliest classfile in the world.
+This class framework has the file suffix `.spx`. It is the earliest class framework in the world.
 
 #### tutorial/01-Weather
 
diff --git a/go.mod b/go.mod
index b87de220..e93a2864 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,11 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.9.0
-	github.com/goplus/gogen v1.17.2
+	github.com/goplus/cobra v1.9.9 //gop:class
+	github.com/goplus/gogen v1.18.0
 	github.com/goplus/lib v0.2.0
-	github.com/goplus/mod v0.16.0
-	github.com/qiniu/x v1.13.19
+	github.com/goplus/mod v0.16.1
+	github.com/qiniu/x v1.14.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index 8bc57fe2..2be26ab7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,15 @@
 github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/goplus/gogen v1.17.2 h1:oQDWiVzZmDAdgNieJnrvrWTmxDTqYXGFMBa2rAbwjmA=
-github.com/goplus/gogen v1.17.2/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
+github.com/goplus/cobra v1.9.9 h1:jUculMMZ5zLSc5tt+8OvJVLzOwd4gaZ73OAnqXAQvf0=
+github.com/goplus/cobra v1.9.9/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E=
+github.com/goplus/gogen v1.18.0 h1:B0PJE/4e8lfRDmrGJbElEB1weokD1vyOyyXb5eZdjfk=
+github.com/goplus/gogen v1.18.0/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
 github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
 github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
-github.com/goplus/mod v0.16.0 h1:5CHXx3no7YaMN5HN2sgZe9MbQnc8118JLjbOeetSTfc=
-github.com/goplus/mod v0.16.0/go.mod h1:U69PUD2e1MnI2DYhuumj1Z6wvZ1bNbKqHUk1MK8Diqo=
-github.com/qiniu/x v1.13.19 h1:rZzWpifNjMtaMhhVnYHw9RGUn+84KGtclDZ9HAKtuZQ=
-github.com/qiniu/x v1.13.19/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
+github.com/goplus/mod v0.16.1 h1:2O+rKubJTn+Fighz3kckwpW87STSZ6s3m/YECfXuNhE=
+github.com/goplus/mod v0.16.1/go.mod h1:8d1P+pBavZfNQtJo4A742DgsLTtSf26BQn51owhmNqI=
+github.com/qiniu/x v1.14.0 h1:Dv30khHR5b1tA4ewilu8lsa5t/NTtGU435FApM4Q/SQ=
+github.com/qiniu/x v1.14.0/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
 golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
 golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
diff --git a/parser/parser.go b/parser/parser.go
index 0d92069e..918c00f9 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1725,6 +1725,31 @@ func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any {
 	return parts
 }
 
+func (p *parser) domainTextLitEx(off, end token.Pos) *ast.DomainTextLitEx {
+	file := p.file
+	base := file.Base()
+	src := p.scanner.CodeTo(int(end) - base)
+
+	var args []ast.Expr
+	var sp parser
+	sp.initSub(file, src, int(off)-base, 0)
+
+	for {
+		expr := sp.parseRHS()
+		args = append(args, expr)
+		if sp.tok != token.COMMA {
+			break
+		}
+		sp.next()
+	}
+	sp.expect(token.SEMICOLON)
+	return &ast.DomainTextLitEx{
+		Args:   args,
+		RawPos: sp.pos,
+		Raw:    string(src[int(sp.pos)-base:]),
+	}
+}
+
 func (p *parser) tplLit(off, end token.Pos) any {
 	file := p.file
 	base := file.Base()
@@ -1760,6 +1785,8 @@ 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`> ...`
+				extra = p.domainTextLitEx(pos+3, pos+token.Pos(len(lit))-1)
 			}
 			x = &ast.DomainTextLit{
 				Domain:   ident,


Test patch:
diff --git a/cl/_testgop/cap/out.go b/cl/_testgop/cap/out.go
index fc418f06..bcf91163 100644
--- a/cl/_testgop/cap/out.go
+++ b/cl/_testgop/cap/out.go
@@ -2,7 +2,7 @@ package main
 
 import (
 	"fmt"
-	"github.com/goplus/gop/builtin/stringslice"
+	"github.com/qiniu/x/stringslice"
 )
 
 func main() {
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/_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/_testgop/enumlines-rdr/in.gop b/cl/_testgop/enumlines-rdr/in.gop
new file mode 100644
index 00000000..3c0834c4
--- /dev/null
+++ b/cl/_testgop/enumlines-rdr/in.gop
@@ -0,0 +1,7 @@
+import "io"
+
+var r io.Reader
+
+for line <- lines(r) {
+	println line
+}
diff --git a/cl/_testgop/enumlines-rdr/out.go b/cl/_testgop/enumlines-rdr/out.go
new file mode 100644
index 00000000..2d3271a5
--- /dev/null
+++ b/cl/_testgop/enumlines-rdr/out.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"fmt"
+	"github.com/qiniu/x/gop/osx"
+	"io"
+)
+
+var r io.Reader
+
+func main() {
+	for _gop_it := osx.Lines(r).Gop_Enum(); ; {
+		var _gop_ok bool
+		line, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(line)
+	}
+}
diff --git a/cl/_testgop/enumlines-stdin/in.gop b/cl/_testgop/enumlines-stdin/in.gop
new file mode 100644
index 00000000..04a102ba
--- /dev/null
+++ b/cl/_testgop/enumlines-stdin/in.gop
@@ -0,0 +1,5 @@
+import "os"
+
+for line <- os.Stdin {
+	println line
+}
diff --git a/cl/_testgop/enumlines-stdin/out.go b/cl/_testgop/enumlines-stdin/out.go
new file mode 100644
index 00000000..1583826d
--- /dev/null
+++ b/cl/_testgop/enumlines-stdin/out.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+	"fmt"
+	"github.com/qiniu/x/gop/osx"
+	"os"
+)
+
+func main() {
+	for _gop_it := osx.EnumLines(os.Stdin); ; {
+		var _gop_ok bool
+		line, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(line)
+	}
+}
diff --git a/cl/_testgop/fatal/in.gop b/cl/_testgop/fatal/in.gop
new file mode 100644
index 00000000..3dd8d1e5
--- /dev/null
+++ b/cl/_testgop/fatal/in.gop
@@ -0,0 +1,8 @@
+import "os"
+
+f, err := os.open("hello.txt")
+if err != nil {
+    errorln "[WARN] an error"
+    fatal "open file failed: ${err}"
+}
+f.close
diff --git a/cl/_testgop/fatal/out.go b/cl/_testgop/fatal/out.go
new file mode 100644
index 00000000..f3f8ed3d
--- /dev/null
+++ b/cl/_testgop/fatal/out.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+	"github.com/qiniu/x/gop/osx"
+	"github.com/qiniu/x/stringutil"
+	"os"
+)
+
+func main() {
+	f, err := os.Open("hello.txt")
+	if err != nil {
+		osx.Errorln("[WARN] an error")
+		osx.Fatal(stringutil.Concat("open file failed: ", err.Error()))
+	}
+	f.Close()
+}
diff --git a/cl/_testgop/rangeexpr/in.gop b/cl/_testgop/rangeexpr/in.gop
new file mode 100644
index 00000000..45eb6500
--- /dev/null
+++ b/cl/_testgop/rangeexpr/in.gop
@@ -0,0 +1,1 @@
+println [x for x <- 0:3:1]
diff --git a/cl/_testgop/rangeexpr/out.go b/cl/_testgop/rangeexpr/out.go
new file mode 100644
index 00000000..b3877d1c
--- /dev/null
+++ b/cl/_testgop/rangeexpr/out.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"fmt"
+	"github.com/qiniu/x/gop"
+)
+
+func main() {
+	fmt.Println(func() (_gop_ret []int) {
+		for _gop_it := gop.NewRange__0(0, 3, 1).Gop_Enum(); ; {
+			var _gop_ok bool
+			x, _gop_ok := _gop_it.Next()
+			if !_gop_ok {
+				break
+			}
+			_gop_ret = append(_gop_ret, x)
+		}
+		return
+	}())
+}
diff --git a/cl/_testgop/repeatuntil/in.gop b/cl/_testgop/repeatuntil/in.gop
new file mode 100644
index 00000000..756f4391
--- /dev/null
+++ b/cl/_testgop/repeatuntil/in.gop
@@ -0,0 +1,12 @@
+func RepeatUntil(cond func() bool, body func()) {
+    for !cond() {
+        body()
+    }
+}
+
+x := 0
+repeatUntil x >= 3, => {
+    echo x
+    x++
+}
+repeatUntil func() bool { return false }, => {}
diff --git a/cl/_testgop/repeatuntil/out.go b/cl/_testgop/repeatuntil/out.go
new file mode 100644
index 00000000..725c50f4
--- /dev/null
+++ b/cl/_testgop/repeatuntil/out.go
@@ -0,0 +1,22 @@
+package main
+
+import "fmt"
+
+func RepeatUntil(cond func() bool, body func()) {
+	for !cond() {
+		body()
+	}
+}
+func main() {
+	x := 0
+	RepeatUntil(func() bool {
+		return x >= 3
+	}, func() {
+		fmt.Println(x)
+		x++
+	})
+	RepeatUntil(func() bool {
+		return false
+	}, func() {
+	})
+}
diff --git a/cl/_testspx/init/init.tspx b/cl/_testspx/init/init.tspx
new file mode 100644
index 00000000..e69de29b
diff --git a/cl/_testspx/init/out.go b/cl/_testspx/init/out.go
new file mode 100644
index 00000000..56ae55c6
--- /dev/null
+++ b/cl/_testspx/init/out.go
@@ -0,0 +1,20 @@
+package main
+
+import "github.com/goplus/gop/cl/internal/spx"
+
+type _init struct {
+	spx.Sprite
+	*MyGame
+}
+type MyGame struct {
+	*spx.MyGame
+}
+
+func (this *MyGame) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
+func (this *_init) Main() {
+}
+func main() {
+	new(MyGame).Main()
+}
diff --git a/cl/_testspx/multiworks/out.go b/cl/_testspx/multiworks/out.go
index 33c5326a..6956d519 100644
--- a/cl/_testspx/multiworks/out.go
+++ b/cl/_testspx/multiworks/out.go
@@ -6,25 +6,31 @@ type foo struct {
 	mcp.Prompt
 	*Game
 }
-type hello struct {
+type Tool_hello struct {
 	mcp.Tool
 	*Game
 }
 type Game struct {
 	mcp.Game
+	foo *foo
 }
 
 func (this *Game) MainEntry() {
 	this.Server("protos")
 }
 func (this *Game) Main() {
-	mcp.Gopt_Game_Main(this, nil, []mcp.ToolProto{new(hello)}, []mcp.PromptProto{new(foo)})
+	_gop_obj0 := &Tool_hello{Game: this}
+	_gop_lst1 := []mcp.ToolProto{_gop_obj0}
+	_gop_obj1 := &foo{Game: this}
+	this.foo = _gop_obj1
+	_gop_lst2 := []mcp.PromptProto{_gop_obj1}
+	mcp.Gopt_Game_Main(this, nil, _gop_lst1, _gop_lst2)
 }
 func (this *foo) Main(_gop_arg0 *mcp.Tool) string {
 	this.Prompt.Main(_gop_arg0)
 	return "Hi"
 }
-func (this *hello) Main(_gop_arg0 string) int {
+func (this *Tool_hello) Main(_gop_arg0 string) int {
 	this.Tool.Main(_gop_arg0)
 	return -1
 }
diff --git a/cl/_testspx/newobj/Kai_spx.gox b/cl/_testspx/newobj/Kai_spx.gox
new file mode 100644
index 00000000..e69de29b
diff --git a/cl/_testspx/newobj/main_spx.gox b/cl/_testspx/newobj/main_spx.gox
new file mode 100644
index 00000000..11211982
--- /dev/null
+++ b/cl/_testspx/newobj/main_spx.gox
@@ -0,0 +1,4 @@
+a := new
+a.run
+b := new(Sprite)
+echo b.name
diff --git a/cl/_testspx/newobj/out.go b/cl/_testspx/newobj/out.go
new file mode 100644
index 00000000..877f203c
--- /dev/null
+++ b/cl/_testspx/newobj/out.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx3"
+)
+
+type Kai struct {
+	spx3.Sprite
+	*Game
+}
+type Game struct {
+	spx3.Game
+}
+
+func (this *Game) MainEntry() {
+	a := spx3.New()
+	a.Run()
+	b := new(spx3.Sprite)
+	fmt.Println(b.Name())
+}
+func (this *Game) Main() {
+	_gop_obj0 := &Kai{Game: this}
+	spx3.Gopt_Game_Main(this, _gop_obj0)
+}
+func (this *Kai) Main(_gop_arg0 string) {
+	this.Sprite.Main(_gop_arg0)
+}
+func (this *Kai) Classfname() string {
+	return "Kai"
+}
+func (this *Kai) Classclone() spx3.Handler {
+	_gop_ret := *this
+	return &_gop_ret
+}
+func main() {
+	new(Game).Main()
+}
diff --git a/cl/_testspx/singlework/out.go b/cl/_testspx/singlework/out.go
index 5cd9b950..158ca9db 100644
--- a/cl/_testspx/singlework/out.go
+++ b/cl/_testspx/singlework/out.go
@@ -19,7 +19,8 @@ func (this *Game) MainEntry() {
 	this.Run()
 }
 func (this *Game) Main() {
-	spx3.Gopt_Game_Main(this, new(Kai))
+	_gop_obj0 := &Kai{Game: this}
+	spx3.Gopt_Game_Main(this, _gop_obj0)
 }
 func (this *Kai) Main(_gop_arg0 string) {
 	this.Sprite.Main(_gop_arg0)
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 5bd2b25c..f6a1914e 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,6 +39,13 @@ func getGoxConf() *gogen.Config {
 	return &gogen.Config{Fset: fset, Importer: imp}
 }
 
+func TestNonClosure(t *testing.T) {
+	tn := types.NewTypeName(0, nil, "a", nil)
+	if !nonClosure(types.NewNamed(tn, types.Typ[types.Int], nil)) {
+		t.Fatal("nonClosure")
+	}
+}
+
 func TestLoadExpr(t *testing.T) {
 	var ni nodeInterp
 	if v := ni.LoadExpr(&ast.Ident{Name: "x"}); v != "" {
@@ -48,7 +55,7 @@ func TestLoadExpr(t *testing.T) {
 
 func TestSpriteOf(t *testing.T) {
 	proj := &gmxProject{}
-	if proj.spriteOf("a") != nil {
+	if getSpxObj(proj, "a") != nil {
 		t.Fatal("spriteOf: not nil?")
 	}
 }
diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go
index c03c5634..44000f38 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 (
@@ -99,8 +100,8 @@ func LookupClass(ext string) (c *modfile.Project, ok bool) {
 		return &modfile.Project{
 			Ext: "_mcp.gox", Class: "Game",
 			Works: []*modfile.Class{
-				{Ext: "_tool.gox", Class: "Tool", Proto: "ToolProto"},
-				{Ext: "_prompt.gox", Class: "Prompt", Proto: "PromptProto"},
+				{Ext: "_tool.gox", Class: "Tool", Proto: "ToolProto", Prefix: "Tool_"},
+				{Ext: "_prompt.gox", Class: "Prompt", Proto: "PromptProto", Embedded: true},
 				{Ext: "_res.gox", Class: "Resource", Proto: "ResourceProto"},
 			},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/mcp"}}, true
@@ -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/cl/compile_gop_test.go b/cl/compile_gop_test.go
index 3a799df7..fa1c1b7d 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -1050,13 +1050,13 @@ for line <- open("foo.txt")! {
 
 import (
 	"fmt"
-	"github.com/goplus/gop/builtin/iox"
 	"github.com/qiniu/x/errors"
+	"github.com/qiniu/x/gop/osx"
 	"os"
 )
 
 func main() {
-	for _gop_it := iox.EnumLines(func() (_gop_ret *os.File) {
+	for _gop_it := osx.EnumLines(func() (_gop_ret *os.File) {
 		var _gop_err error
 		_gop_ret, _gop_err = os.Open("foo.txt")
 		if _gop_err != nil {
@@ -1076,66 +1076,6 @@ func main() {
 `)
 }
 
-func TestFileEnumLines(t *testing.T) {
-	gopClTest(t, `
-import "os"
-
-for line <- os.Stdin {
-	println line
-}
-`, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/builtin/iox"
-	"os"
-)
-
-func main() {
-	for _gop_it := iox.EnumLines(os.Stdin); ; {
-		var _gop_ok bool
-		line, _gop_ok := _gop_it.Next()
-		if !_gop_ok {
-			break
-		}
-		fmt.Println(line)
-	}
-}
-`)
-}
-
-func TestIoxLines(t *testing.T) {
-	gopClTest(t, `
-import "io"
-
-var r io.Reader
-
-for line <- lines(r) {
-	println line
-}
-`, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/builtin/iox"
-	"io"
-)
-
-var r io.Reader
-
-func main() {
-	for _gop_it := iox.Lines(r).Gop_Enum(); ; {
-		var _gop_ok bool
-		line, _gop_ok := _gop_it.Next()
-		if !_gop_ok {
-			break
-		}
-		fmt.Println(line)
-	}
-}
-`)
-}
-
 func TestMixedGo(t *testing.T) {
 	gopMixedClTest(t, "main", `package main
 
@@ -1402,32 +1342,6 @@ func main() {
 `)
 }
 
-func TestRangeExpr7(t *testing.T) {
-	gopClTest(t, `
-println [x for x <- 0:3:1]
-`, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/builtin"
-)
-
-func main() {
-	fmt.Println(func() (_gop_ret []int) {
-		for _gop_it := builtin.NewRange__0(0, 3, 1).Gop_Enum(); ; {
-			var _gop_ok bool
-			x, _gop_ok := _gop_it.Next()
-			if !_gop_ok {
-				break
-			}
-			_gop_ret = append(_gop_ret, x)
-		}
-		return
-	}())
-}
-`)
-}
-
 func testRangeExpr8(t *testing.T, codeTpl, expect string) {
 	for _, s := range []string{" <- ", " := range "} {
 		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 1e45651d..fffc0c88 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -391,52 +391,6 @@ func main() {
 `, "index.tgmx", "Kai.tspx")
 }
 
-func TestSpxNewObj(t *testing.T) {
-	gopSpxTestEx(t, `
-a := new
-a.run
-b := new(Sprite)
-echo b.name
-`, ``, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/cl/internal/spx3"
-)
-
-type Kai struct {
-	spx3.Sprite
-	*Game
-}
-type Game struct {
-	spx3.Game
-}
-
-func (this *Game) MainEntry() {
-	a := spx3.New()
-	a.Run()
-	b := new(spx3.Sprite)
-	fmt.Println(b.Name())
-}
-func (this *Game) Main() {
-	spx3.Gopt_Game_Main(this, new(Kai))
-}
-func (this *Kai) Main(_gop_arg0 string) {
-	this.Sprite.Main(_gop_arg0)
-}
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
-func (this *Kai) Classclone() spx3.Handler {
-	_gop_ret := *this
-	return &_gop_ret
-}
-func main() {
-	new(Game).Main()
-}
-`, "main_spx.gox", "Kai_spx.gox")
-}
-
 func TestSpx2(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi")
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 3cabd154..323ff405 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -1469,7 +1469,7 @@ var x, y uint128
 var z uint128 = x + y
 `, `package main
 
-import "github.com/goplus/gop/builtin/ng"
+import "github.com/qiniu/x/gop/ng"
 
 var x, y ng.Uint128
 var z ng.Uint128 = (ng.Uint128).Gop_Add__1(x, y)
@@ -1482,7 +1482,7 @@ var x, y int128
 var z int128 = x + y
 `, `package main
 
-import "github.com/goplus/gop/builtin/ng"
+import "github.com/qiniu/x/gop/ng"
 
 var x, y ng.Int128
 var z ng.Int128 = (ng.Int128).Gop_Add__1(x, y)
@@ -1495,7 +1495,7 @@ var x, y bigint
 var z bigint = x + y
 `, `package main
 
-import "github.com/goplus/gop/builtin/ng"
+import "github.com/qiniu/x/gop/ng"
 
 var x, y ng.Bigint
 var z ng.Bigint = (ng.Bigint).Gop_Add(x, y)
@@ -1508,7 +1508,7 @@ var x = 1r
 `, `package main
 
 import (
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
@@ -1521,7 +1521,7 @@ func TestUint128Lit(t *testing.T) {
 var x uint128 = 1
 `, `package main
 
-import "github.com/goplus/gop/builtin/ng"
+import "github.com/qiniu/x/gop/ng"
 
 var x ng.Uint128 = ng.Uint128_Init__0(1)
 `)
@@ -1532,7 +1532,7 @@ func TestInt128Lit(t *testing.T) {
 var x int128 = 1
 `, `package main
 
-import "github.com/goplus/gop/builtin/ng"
+import "github.com/qiniu/x/gop/ng"
 
 var x ng.Int128 = ng.Int128_Init__0(1)
 `)
@@ -1544,7 +1544,7 @@ var x = 1/2r
 `, `package main
 
 import (
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
@@ -1558,7 +1558,7 @@ var x = 3 + 1/2r
 `, `package main
 
 import (
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
@@ -1575,7 +1575,7 @@ var z = 100 + y
 `, `package main
 
 import (
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
@@ -1688,7 +1688,7 @@ var x bigint
 x += 3
 `, `package main
 
-import "github.com/goplus/gop/builtin/ng"
+import "github.com/qiniu/x/gop/ng"
 
 var x ng.Bigint
 
@@ -1705,7 +1705,7 @@ x *= 2
 `, `package main
 
 import (
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
@@ -1723,7 +1723,7 @@ x *= 2r
 `, `package main
 
 import (
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
diff --git a/cl/compile_testdir_test.go b/cl/compile_testdir_test.go
index e0035cdd..c699ab01 100644
--- a/cl/compile_testdir_test.go
+++ b/cl/compile_testdir_test.go
@@ -27,7 +27,7 @@ func TestTestspx(t *testing.T) {
 }
 
 func TestTestgop(t *testing.T) {
-	cltest.FromDir(t, "", "./_testgop")
+	cltest.FromDir(t, "fatal", "./_testgop")
 }
 
 func TestTestc(t *testing.T) {
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index f9099173..a3d03389 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -987,7 +987,7 @@ func test() {
 }
 
 func TestErrInt128(t *testing.T) {
-	codeErrorTest(t, `bar.gop:2:16: cannot use 1<<127 (type untyped int) as type github.com/goplus/gop/builtin/ng.Int128 in assignment`, `
+	codeErrorTest(t, `bar.gop:2:16: cannot use 1<<127 (type untyped int) as type github.com/qiniu/x/gop/ng.Int128 in assignment`, `
 var a int128 = 1<<127
 `)
 	codeErrorTest(t, `bar.gop:2:13: cannot convert 1<<127 (untyped int constant 170141183460469231731687303715884105728) to type Int128`, `
@@ -1003,13 +1003,13 @@ a := int128(b)
 }
 
 func TestErrUint128(t *testing.T) {
-	codeErrorTest(t, `bar.gop:2:17: cannot use 1<<128 (type untyped int) as type github.com/goplus/gop/builtin/ng.Uint128 in assignment`, `
+	codeErrorTest(t, `bar.gop:2:17: cannot use 1<<128 (type untyped int) as type github.com/qiniu/x/gop/ng.Uint128 in assignment`, `
 var a uint128 = 1<<128
 `)
 	codeErrorTest(t, `bar.gop:2:14: cannot convert 1<<128 (untyped int constant 340282366920938463463374607431768211456) to type Uint128`, `
 a := uint128(1<<128)
 `)
-	codeErrorTest(t, `bar.gop:2:17: cannot use -1 (type untyped int) as type github.com/goplus/gop/builtin/ng.Uint128 in assignment`, `
+	codeErrorTest(t, `bar.gop:2:17: cannot use -1 (type untyped int) as type github.com/qiniu/x/gop/ng.Uint128 in assignment`, `
 var a uint128 = -1
 `)
 	codeErrorTest(t, `bar.gop:2:14: cannot convert -1 (untyped int constant -1) to type Uint128`, `
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index e78e1d3b..b55ba4fc 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -767,3 +767,80 @@ func main() {
 }
 `)
 }
+
+func TestGoptLambdaFunc(t *testing.T) {
+	gopClTest(t, `
+import "github.com/goplus/gop/cl/internal/overload/bar"
+
+type Message struct {
+	info string
+}
+p := &bar.Player{}
+p.onCmd Message, msg => {
+	echo msg.info
+	return nil
+}
+p.onCmd int, Message, 100, (n,msg) => {
+	echo n, msg.info
+	return nil
+}
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/overload/bar"
+)
+
+type Message struct {
+	info string
+}
+
+func main() {
+	p := &bar.Player{}
+	bar.Gopt_Player_Gopx_OnCmd__0[Message](p, func(msg Message) error {
+		fmt.Println(msg.info)
+		return nil
+	})
+	bar.Gopt_Player_Gopx_OnCmd__1[int, Message](p, 100, func(n int, msg Message) error {
+		fmt.Println(n, msg.info)
+		return nil
+	})
+}
+`)
+}
+
+func TestGoptLambdaError(t *testing.T) {
+	codeErrorTest(t, `bar.gop:8:9: 100 not type`, `
+import "github.com/goplus/gop/cl/internal/overload/bar"
+
+type Message struct {
+	info string
+}
+p := &bar.Player{}
+p.onCmd 100, Message, 100, (n, msg) => {
+	echo msg.info
+	return nil
+}
+`)
+	var msg string
+	switch runtime.Version()[:6] {
+	case "go1.18":
+		msg = "bar.gop:8:1: string does not implement ~int"
+	case "go1.19":
+		msg = "bar.gop:8:1: string does not implement ~int (string missing in ~int)"
+	default:
+		msg = "bar.gop:8:1: string does not satisfy ~int (string missing in ~int)"
+	}
+	codeErrorTest(t, msg, `
+import "github.com/goplus/gop/cl/internal/overload/bar"
+
+type Message struct {
+	info string
+}
+p := &bar.Player{}
+p.onCmd string, Message,"hello",(n, msg) => {
+	echo msg.info
+	return nil
+}
+`)
+}
diff --git a/cmd/xgo/test_cmd.gox b/cmd/xgo/test_cmd.gox
new file mode 100644
index 00000000..6e980462
--- /dev/null
+++ b/cmd/xgo/test_cmd.gox
@@ -0,0 +1,26 @@
+/*
+ * 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 (
+	self "github.com/goplus/gop/cmd/internal/test"
+)
+
+short "Test Go+ packages"
+
+flagOff
+
+run args => {
+	self.Cmd.Run self.Cmd, args
+}
diff --git a/parser/_testdata/domainhuh/huh.gop b/parser/_testdata/domainhuh/huh.gop
new file mode 100644
index 00000000..d470f579
--- /dev/null
+++ b/parser/_testdata/domainhuh/huh.gop
@@ -0,0 +1,12 @@
+form := huh`> &ret, 10
+<form>
+	<group>
+		<select id="Burger" title="Choose your burger">
+			<option value="classic" title="Charmburger Classic"/>
+			<option value="chickwich" title="Chickwich"/>
+			<option value="fishburger" title="Fishburger"/>
+			<option value="charmpossible" title="Charmpossible™ Burger"/>
+		</select>
+	</group>
+</form>
+`
diff --git a/parser/_testdata/domainhuh/parser.expect b/parser/_testdata/domainhuh/parser.expect
new file mode 100644
index 00000000..ffb01ccf
--- /dev/null
+++ b/parser/_testdata/domainhuh/parser.expect
@@ -0,0 +1,57 @@
+package main
+
+file huh.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: form
+          Tok: :=
+          Rhs:
+            ast.DomainTextLit:
+              Domain:
+                ast.Ident:
+                  Name: huh
+              Value: `> &ret, 10
+<form>
+	<group>
+		<select id="Burger" title="Choose your burger">
+			<option value="classic" title="Charmburger Classic"/>
+			<option value="chickwich" title="Chickwich"/>
+			<option value="fishburger" title="Fishburger"/>
+			<option value="charmpossible" title="Charmpossible™ Burger"/>
+		</select>
+	</group>
+</form>
+`
+                Extra: args=2
+                  ast.UnaryExpr:
+                    Op: &
+                    X:
+                      ast.Ident:
+                        Name: ret
+                  ast.BasicLit:
+                    Kind: INT
+                    Value: 10
+                  <form>
+	<group>
+		<select id="Burger" title="Choose your burger">
+			<option value="classic" title="Charmburger Classic"/>
+			<option value="chickwich" title="Chickwich"/>
+			<option value="fishburger" title="Fishburger"/>
+			<option value="charmpossible" title="Charmpossible™ Burger"/>
+		</select>
+	</group>
+</form>
+
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 697a2129..58febdde 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -116,11 +116,8 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
 		t.Fatal("ParseDir failed:", err, reflect.TypeOf(err), len(pkgs))
 	}
 	for _, pkg := range pkgs {
-		b, err := os.ReadFile(pkgDir + "/parser.expect")
-		if err != nil {
-			t.Fatal("Parsing", pkgDir, "-", err)
-		}
-		parsertest.Expect(t, pkg, string(b))
+		b, _ := os.ReadFile(pkgDir + "/parser.expect")
+		parsertest.ExpectEx(t, pkgDir+"/result.txt", pkg, b)
 		return
 	}
 }
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index d3bc6d1d..f8c1dee9 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -30,6 +30,7 @@ import (
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
 	tpltoken "github.com/goplus/gop/tpl/token"
+	"github.com/qiniu/x/test"
 )
 
 func sortedKeys(m any) []string {
@@ -102,6 +103,13 @@ func FprintNode(w io.Writer, lead string, v any, prefix, indent string) {
 					FprintNode(w, "", part, prefix, indent)
 				}
 			}
+		} else if lit, ok := v.(*ast.DomainTextLitEx); ok {
+			fmt.Fprintf(w, "%sExtra: args=%d\n", prefix, len(lit.Args))
+			prefix += indent
+			for _, arg := range lit.Args {
+				FprintNode(w, "", arg, prefix, indent)
+			}
+			fmt.Fprintf(w, "%s%v\n", prefix, lit.Raw)
 		} else {
 			log.Panicln("FprintNode unexpected type:", t)
 		}
@@ -137,6 +145,14 @@ func Expect(t *testing.T, pkg *ast.Package, expected string) {
 	}
 }
 
+func ExpectEx(t *testing.T, outfile string, pkg *ast.Package, expected []byte) {
+	b := bytes.NewBuffer(nil)
+	Fprint(b, pkg)
+	if test.Diff(t, outfile, b.Bytes(), expected) {
+		t.Fatal("gop.Parser: unexpect result")
+	}
+}
+
 // ExpectNode asserts a Go+ AST node equals output or not.
 func ExpectNode(t *testing.T, node any, expected string) {
 	b := bytes.NewBuffer(nil)
diff --git a/x/build/build_test.go b/x/build/build_test.go
index 3c03bbb5..8f637a7c 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -240,7 +240,7 @@ println a+1/2r
 
 import (
 	"fmt"
-	"github.com/goplus/gop/builtin/ng"
+	"github.com/qiniu/x/gop/ng"
 	"math/big"
 )
 
@@ -264,14 +264,14 @@ for line <- lines(r) {
 
 import (
 	"fmt"
-	"github.com/goplus/gop/builtin/iox"
+	"github.com/qiniu/x/gop/osx"
 	"io"
 )
 
 var r io.Reader
 
 func main() {
-	for _gop_it := iox.Lines(r).Gop_Enum(); ; {
+	for _gop_it := osx.Lines(r).Gop_Enum(); ; {
 		var _gop_ok bool
 		line, _gop_ok := _gop_it.Next()
 		if !_gop_ok {