diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
deleted file mode 100644
index 12e07a9cde6..00000000000
--- a/.github/pull_request_template.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-Contributors' checklist...
-
-- [ ] Added new tests, or not needed, or not feasible
-- [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory
-- [ ] Updated the official documentation or not needed
-- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description
-- [ ] Added references to related issues and PRs
-- [ ] Provided any useful hints for running manual tests
-
diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml
index 644540c1aaf..300a5928e25 100644
--- a/.github/workflows/bot.yml
+++ b/.github/workflows/bot.yml
@@ -14,6 +14,10 @@ on:
- converted_to_draft
- ready_for_review
+ # Watch for changes on PR reviews
+ pull_request_review:
+ types: [submitted, edited, dismissed]
+
# Watch for changes on PR comment
issue_comment:
types: [created, edited, deleted]
diff --git a/contribs/github-bot/internal/check/check.go b/contribs/github-bot/internal/check/check.go
index 5ca2235e823..cb1848b757c 100644
--- a/contribs/github-bot/internal/check/check.go
+++ b/contribs/github-bot/internal/check/check.go
@@ -101,7 +101,8 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error {
go func(pr *github.PullRequest) {
defer wg.Done()
commentContent := CommentContent{}
- commentContent.allSatisfied = true
+ commentContent.AutoAllSatisfied = true
+ commentContent.ManualAllSatisfied = true
// Iterate over all automatic rules in config.
for _, autoRule := range autoRules {
@@ -120,7 +121,7 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error {
thenDetails.SetValue(fmt.Sprintf("%s Requirement satisfied", utils.Success))
c.Satisfied = true
} else {
- commentContent.allSatisfied = false
+ commentContent.AutoAllSatisfied = false
}
c.ConditionDetails = ifDetails.String()
@@ -160,8 +161,14 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error {
},
)
- if checkedBy == "" {
- commentContent.allSatisfied = false
+ // If this check is the special one, store its state in the dedicated var.
+ if manualRule.Description == config.ForceSkipDescription {
+ if checkedBy != "" {
+ commentContent.ForceSkip = true
+ }
+ } else if checkedBy == "" {
+ // Or if its a normal check, just verify if it was checked by someone.
+ commentContent.ManualAllSatisfied = false
}
}
@@ -224,9 +231,20 @@ func logResults(logger logger.Logger, prNum int, commentContent CommentContent)
}
logger.Infof("Conclusion:")
- if commentContent.allSatisfied {
- logger.Infof("%s All requirements are satisfied\n", utils.Success)
+
+ if commentContent.AutoAllSatisfied {
+ logger.Infof("%s All automated checks are satisfied", utils.Success)
+ } else {
+ logger.Infof("%s Some automated checks are not satisfied", utils.Fail)
+ }
+
+ if commentContent.ManualAllSatisfied {
+ logger.Infof("%s All manual checks are satisfied\n", utils.Success)
} else {
- logger.Infof("%s Not all requirements are satisfied\n", utils.Fail)
+ logger.Infof("%s Some manual checks are not satisfied\n", utils.Fail)
+ }
+
+ if commentContent.ForceSkip {
+ logger.Infof("%s Bot checks are force skipped\n", utils.Success)
}
}
diff --git a/contribs/github-bot/internal/check/comment.go b/contribs/github-bot/internal/check/comment.go
index 297395ffe4b..d2b386cfa2e 100644
--- a/contribs/github-bot/internal/check/comment.go
+++ b/contribs/github-bot/internal/check/comment.go
@@ -24,9 +24,9 @@ var errTriggeredByBot = errors.New("event triggered by bot")
// Compile regex only once.
var (
// Regex for capturing the entire line of a manual check.
- manualCheckLine = regexp.MustCompile(`(?m:^-\s\[([ xX])\]\s+(.+?)\s*(\(checked by @(\w+)\))?$)`)
+ manualCheckLine = regexp.MustCompile(`(?m:^- \[([ xX])\] (.+?)(?: \(checked by @([A-Za-z0-9-]+)\))?$)`)
// Regex for capturing only the checkboxes.
- checkboxes = regexp.MustCompile(`(?m:^- \[[ x]\])`)
+ checkboxes = regexp.MustCompile(`(?m:^- \[[ xX]\])`)
// Regex used to capture markdown links.
markdownLink = regexp.MustCompile(`\[(.*)\]\([^)]*\)`)
)
@@ -46,9 +46,11 @@ type ManualContent struct {
Teams []string
}
type CommentContent struct {
- AutoRules []AutoContent
- ManualRules []ManualContent
- allSatisfied bool
+ AutoRules []AutoContent
+ ManualRules []ManualContent
+ AutoAllSatisfied bool
+ ManualAllSatisfied bool
+ ForceSkip bool
}
type manualCheckDetails struct {
@@ -64,10 +66,10 @@ func getCommentManualChecks(commentBody string) map[string]manualCheckDetails {
// For each line that matches the "Manual check" regex.
for _, match := range manualCheckLine.FindAllStringSubmatch(commentBody, -1) {
description := match[2]
- status := match[1]
+ status := strings.ToLower(match[1]) // if X captured, convert it to x.
checkedBy := ""
- if len(match) > 4 {
- checkedBy = strings.ToLower(match[4]) // if X captured, convert it to x.
+ if len(match) > 3 {
+ checkedBy = match[3]
}
checks[description] = manualCheckDetails{status: status, checkedBy: checkedBy}
@@ -261,13 +263,15 @@ func updatePullRequest(gh *client.GitHub, pr *github.PullRequest, content Commen
var (
context = "Merge Requirements"
targetURL = comment.GetHTMLURL()
- state = "failure"
- description = "Some requirements are not satisfied yet. See bot comment."
+ state = "success"
+ description = "All requirements are satisfied."
)
- if content.allSatisfied {
- state = "success"
- description = "All requirements are satisfied."
+ if content.ForceSkip {
+ description = "Bot checks are skipped for this PR."
+ } else if !content.AutoAllSatisfied || !content.ManualAllSatisfied {
+ state = "failure"
+ description = "Some requirements are not satisfied yet. See bot comment."
}
// Update or create commit status.
diff --git a/contribs/github-bot/internal/check/comment.tmpl b/contribs/github-bot/internal/check/comment.tmpl
index 4312019dd2e..d9b633a69d5 100644
--- a/contribs/github-bot/internal/check/comment.tmpl
+++ b/contribs/github-bot/internal/check/comment.tmpl
@@ -1,19 +1,34 @@
-I'm a bot that assists the Gno Core team in maintaining this repository. My role is to ensure that contributors understand and follow our guidelines, helping to streamline the development process.
+#### 🛠 PR Checks Summary
+{{ if and .AutoRules (not .AutoAllSatisfied) }}{{ range .AutoRules }}{{ if not .Satisfied }} 🔴 {{ .Description }}
+{{end}}{{end}}{{ else }}All **Automated Checks** passed. ✅{{end}}
-The following requirements must be fulfilled before a pull request can be merged.
-Some requirement checks are automated and can be verified by the CI, while others need manual verification by a staff member.
+##### Manual Checks (for Reviewers):
+{{ if .ManualRules }}{{ range .ManualRules }}- [{{ if .CheckedBy }}x{{ else }} {{ end }}] {{ .Description }}{{ if .CheckedBy }} (checked by @{{ .CheckedBy }}){{ end }}
+{{ end }}{{ else }}*No manual checks match this pull request.*{{ end }}
-These requirements are defined in this [configuration file](https://github.com/gnolang/gno/tree/master/contribs/github-bot/internal/config/config.go).
+Read More
-## Automated Checks
+🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers.
+##### ✅ Automated Checks (for Contributors):
{{ if .AutoRules }}{{ range .AutoRules }} {{ if .Satisfied }}🟢{{ else }}🔴{{ end }} {{ .Description }}
{{ end }}{{ else }}*No automated checks match this pull request.*{{ end }}
-## Manual Checks
+##### ☑️ Contributor Actions:
+1. Fix any issues flagged by automated checks.
+2. Follow the Contributor Checklist to ensure your PR is ready for review.
+ - Add new tests, or document why they are unnecessary.
+ - Provide clear examples/screenshots, if necessary.
+ - Update documentation, if required.
+ - Ensure no breaking changes, or include `BREAKING CHANGE` notes.
+ - Link related issues/PRs, where applicable.
-{{ if .ManualRules }}{{ range .ManualRules }}- [{{ if .CheckedBy }}x{{ else }} {{ end }}] {{ .Description }}{{ if .CheckedBy }} (checked by @{{ .CheckedBy }}){{ end }}
-{{ end }}{{ else }}*No manual checks match this pull request.*{{ end }}
+##### ☑️ Reviewer Actions:
+1. Complete manual checks for the PR, including the guidelines and additional checks if applicable.
+
+##### 📚 Resources:
+- [Report a bug with the bot](https://github.com/gnolang/gno/issues/3238).
+- [View the bot’s configuration file](https://github.com/gnolang/gno/tree/master/contribs/github-bot/internal/config/config.go).
{{ if or .AutoRules .ManualRules }}Debug
{{ if .AutoRules }}Automated Checks
@@ -52,3 +67,4 @@ These requirements are defined in this [configuration file](https://github.com/g
{{ end }}
{{ end }}
+
diff --git a/contribs/github-bot/internal/check/comment_test.go b/contribs/github-bot/internal/check/comment_test.go
index 0334b76f95c..29886f80f43 100644
--- a/contribs/github-bot/internal/check/comment_test.go
+++ b/contribs/github-bot/internal/check/comment_test.go
@@ -31,31 +31,44 @@ func TestGeneratedComment(t *testing.T) {
{Description: "Test automatic 5", Satisfied: false},
}
manualRules := []ManualContent{
- {Description: "Test manual 1", CheckedBy: "user_1"},
+ {Description: "Test manual 1", CheckedBy: "user-1"},
{Description: "Test manual 2", CheckedBy: ""},
{Description: "Test manual 3", CheckedBy: ""},
- {Description: "Test manual 4", CheckedBy: "user_4"},
- {Description: "Test manual 5", CheckedBy: "user_5"},
+ {Description: "Test manual 4", CheckedBy: "user-4"},
+ {Description: "Test manual 5", CheckedBy: "user-5"},
}
commentText, err := generateComment(content)
assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err))
assert.True(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should contains automated check placeholder")
assert.True(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should contains manual check placeholder")
+ assert.True(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder")
content.AutoRules = autoRules
+ content.AutoAllSatisfied = true
commentText, err = generateComment(content)
assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err))
assert.False(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should not contains automated check placeholder")
assert.True(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should contains manual check placeholder")
+ assert.True(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder")
assert.Equal(t, 2, len(autoCheckSuccessLine.FindAllStringSubmatch(commentText, -1)), "wrong number of succeeded automatic check")
assert.Equal(t, 3, len(autoCheckFailLine.FindAllStringSubmatch(commentText, -1)), "wrong number of failed automatic check")
+ content.AutoAllSatisfied = false
+ commentText, err = generateComment(content)
+ assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err))
+ assert.False(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should not contains automated check placeholder")
+ assert.True(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should contains manual check placeholder")
+ assert.False(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder")
+ assert.Equal(t, 2, len(autoCheckSuccessLine.FindAllStringSubmatch(commentText, -1)), "wrong number of succeeded automatic check")
+ assert.Equal(t, 3+3, len(autoCheckFailLine.FindAllStringSubmatch(commentText, -1)), "wrong number of failed automatic check")
+
content.ManualRules = manualRules
commentText, err = generateComment(content)
assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err))
assert.False(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should not contains automated check placeholder")
assert.False(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should not contains manual check placeholder")
+ assert.False(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder")
manualChecks := getCommentManualChecks(commentText)
assert.Equal(t, len(manualChecks), len(manualRules), "wrong number of manual checks found")
diff --git a/contribs/github-bot/internal/conditions/fork.go b/contribs/github-bot/internal/conditions/fork.go
new file mode 100644
index 00000000000..72cbae12004
--- /dev/null
+++ b/contribs/github-bot/internal/conditions/fork.go
@@ -0,0 +1,27 @@
+package conditions
+
+import (
+ "fmt"
+
+ "github.com/gnolang/gno/contribs/github-bot/internal/utils"
+
+ "github.com/google/go-github/v64/github"
+ "github.com/xlab/treeprint"
+)
+
+// CreatedFromFork Condition.
+type createdFromFork struct{}
+
+var _ Condition = &createdFromFork{}
+
+func (b *createdFromFork) IsMet(pr *github.PullRequest, details treeprint.Tree) bool {
+ return utils.AddStatusNode(
+ pr.GetHead().GetRepo().GetFullName() != pr.GetBase().GetRepo().GetFullName(),
+ fmt.Sprintf("The pull request was created from a fork (head branch repo: %s)", pr.GetHead().GetRepo().GetFullName()),
+ details,
+ )
+}
+
+func CreatedFromFork() Condition {
+ return &createdFromFork{}
+}
diff --git a/contribs/github-bot/internal/conditions/fork_test.go b/contribs/github-bot/internal/conditions/fork_test.go
new file mode 100644
index 00000000000..fe7e9a95bf1
--- /dev/null
+++ b/contribs/github-bot/internal/conditions/fork_test.go
@@ -0,0 +1,31 @@
+package conditions
+
+import (
+ "testing"
+
+ "github.com/gnolang/gno/contribs/github-bot/internal/utils"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/google/go-github/v64/github"
+ "github.com/xlab/treeprint"
+)
+
+func TestCreatedFromFork(t *testing.T) {
+ t.Parallel()
+
+ var (
+ repo = &github.PullRequestBranch{Repo: &github.Repository{Owner: &github.User{Login: github.String("main")}, Name: github.String("repo"), FullName: github.String("main/repo")}}
+ fork = &github.PullRequestBranch{Repo: &github.Repository{Owner: &github.User{Login: github.String("fork")}, Name: github.String("repo"), FullName: github.String("fork/repo")}}
+ )
+
+ prFromMain := &github.PullRequest{Base: repo, Head: repo}
+ prFromFork := &github.PullRequest{Base: repo, Head: fork}
+
+ details := treeprint.New()
+ assert.False(t, CreatedFromFork().IsMet(prFromMain, details))
+ assert.True(t, utils.TestLastNodeStatus(t, false, details), "condition details should have a status: false")
+
+ details = treeprint.New()
+ assert.True(t, CreatedFromFork().IsMet(prFromFork, details))
+ assert.True(t, utils.TestLastNodeStatus(t, true, details), "condition details should have a status: true")
+}
diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go
index c1d89e4cde5..fd29f5e5f57 100644
--- a/contribs/github-bot/internal/config/config.go
+++ b/contribs/github-bot/internal/config/config.go
@@ -22,13 +22,17 @@ type ManualCheck struct {
Teams Teams // Members of these teams can check the checkbox to make the check pass.
}
+// This is the description for a persistent rule with a non-standard behavior
+// that allow maintainer to force the "success" state of the CI check
+const ForceSkipDescription = "**SKIP**: Do not block the CI for this PR"
+
// This function returns the configuration of the bot consisting of automatic and manual checks
// in which the GitHub client is injected.
func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
auto := []AutomaticCheck{
{
Description: "Maintainers must be able to edit this pull request ([more info](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork))",
- If: c.Always(),
+ If: c.CreatedFromFork(),
Then: r.MaintainerCanModify(),
},
{
@@ -53,6 +57,11 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
}
manual := []ManualCheck{
+ {
+ // WARN: Do not edit this special rule which must remain persistent.
+ Description: ForceSkipDescription,
+ If: c.Always(),
+ },
{
Description: "The pull request description provides enough details",
If: c.Not(c.AuthorInTeam(gh, "core-contributors")),
diff --git a/contribs/github-bot/internal/matrix/matrix.go b/contribs/github-bot/internal/matrix/matrix.go
index 9c8f12e4214..02840721c80 100644
--- a/contribs/github-bot/internal/matrix/matrix.go
+++ b/contribs/github-bot/internal/matrix/matrix.go
@@ -113,7 +113,7 @@ func getPRListFromEvent(gh *client.GitHub, actionCtx *githubactions.GitHubContex
// Event triggered by an issue / PR comment being created / edited / deleted
// or any update on a PR.
- case utils.EventIssueComment, utils.EventPullRequest, utils.EventPullRequestTarget:
+ case utils.EventIssueComment, utils.EventPullRequest, utils.EventPullRequestReview, utils.EventPullRequestTarget:
// For these events, retrieve the number of the associated PR from the context.
prNum, err := utils.GetPRNumFromActionsCtx(actionCtx)
if err != nil {
diff --git a/contribs/github-bot/internal/matrix/matrix_test.go b/contribs/github-bot/internal/matrix/matrix_test.go
index fe5b7452a49..f6b34f16c24 100644
--- a/contribs/github-bot/internal/matrix/matrix_test.go
+++ b/contribs/github-bot/internal/matrix/matrix_test.go
@@ -54,6 +54,15 @@ func TestProcessEvent(t *testing.T) {
prs,
utils.PRList{1},
false,
+ }, {
+ "valid pull_request_review event",
+ &githubactions.GitHubContext{
+ EventName: utils.EventPullRequestReview,
+ Event: map[string]any{"pull_request": map[string]any{"number": 1.}},
+ },
+ prs,
+ utils.PRList{1},
+ false,
}, {
"valid pull_request_target event",
&githubactions.GitHubContext{
diff --git a/contribs/github-bot/internal/utils/actions.go b/contribs/github-bot/internal/utils/actions.go
index 3e08a8e1548..0686e8c29c5 100644
--- a/contribs/github-bot/internal/utils/actions.go
+++ b/contribs/github-bot/internal/utils/actions.go
@@ -30,7 +30,7 @@ func GetPRNumFromActionsCtx(actionCtx *githubactions.GitHubContext) (int, error)
switch actionCtx.EventName {
case EventIssueComment:
firstKey = "issue"
- case EventPullRequest, EventPullRequestTarget:
+ case EventPullRequest, EventPullRequestReview, EventPullRequestTarget:
firstKey = "pull_request"
default:
return 0, fmt.Errorf("unsupported event: %s", actionCtx.EventName)
diff --git a/contribs/github-bot/internal/utils/github_const.go b/contribs/github-bot/internal/utils/github_const.go
index 26d7d54d477..f030d9365f7 100644
--- a/contribs/github-bot/internal/utils/github_const.go
+++ b/contribs/github-bot/internal/utils/github_const.go
@@ -5,6 +5,7 @@ const (
// GitHub Actions Event Names.
EventIssueComment = "issue_comment"
EventPullRequest = "pull_request"
+ EventPullRequestReview = "pull_request_review"
EventPullRequestTarget = "pull_request_target"
EventWorkflowDispatch = "workflow_dispatch"
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index c9d6487d753..082d0cb8270 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -59,6 +59,7 @@ type devCfg struct {
// Web Configuration
webListenerAddr string
webRemoteHelperAddr string
+ webWithHTML bool
// Node Configuration
minimal bool
@@ -126,14 +127,21 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
&c.webListenerAddr,
"web-listener",
defaultDevOptions.webListenerAddr,
- "web server listener address",
+ "gnoweb: web server listener address",
)
fs.StringVar(
&c.webRemoteHelperAddr,
"web-help-remote",
defaultDevOptions.webRemoteHelperAddr,
- "web server help page's remote addr (default to )",
+ "gnoweb: web server help page's remote addr (default to )",
+ )
+
+ fs.BoolVar(
+ &c.webWithHTML,
+ "web-with-html",
+ defaultDevOptions.webWithHTML,
+ "gnoweb: enable HTML parsing in markdown rendering",
)
fs.StringVar(
diff --git a/contribs/gnodev/cmd/gnodev/setup_web.go b/contribs/gnodev/cmd/gnodev/setup_web.go
index 635c27af19d..d55814142a6 100644
--- a/contribs/gnodev/cmd/gnodev/setup_web.go
+++ b/contribs/gnodev/cmd/gnodev/setup_web.go
@@ -15,6 +15,7 @@ func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) htt
webConfig.HelpChainID = cfg.chainId
webConfig.RemoteAddr = dnode.GetRemoteAddress()
webConfig.HelpRemote = cfg.webRemoteHelperAddr
+ webConfig.WithHTML = cfg.webWithHTML
// If `HelpRemote` is empty default it to `RemoteAddr`
if webConfig.HelpRemote == "" {
diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod
index a315d88591c..2053a61db6c 100644
--- a/contribs/gnodev/go.mod
+++ b/contribs/gnodev/go.mod
@@ -50,7 +50,6 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum
index e38c3621483..f9250d34462 100644
--- a/contribs/gnodev/go.sum
+++ b/contribs/gnodev/go.sum
@@ -101,8 +101,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod
index c5bb1ad0d81..eab9fc90c50 100644
--- a/contribs/gnofaucet/go.mod
+++ b/contribs/gnofaucet/go.mod
@@ -20,7 +20,6 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum
index f4bdc65d7ec..aabe858e893 100644
--- a/contribs/gnofaucet/go.sum
+++ b/contribs/gnofaucet/go.sum
@@ -49,8 +49,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gnolang/faucet v0.3.2 h1:3QBrdmnQszRaAZbxgO5xDDm3czNa0L/RFmhnCkbxy5I=
github.com/gnolang/faucet v0.3.2/go.mod h1:/wbw9h4ooMzzyNBuM0X+ol7CiPH2OFjAFF3bYAXqA7U=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod
index b777cc6e5eb..f1b316c2bee 100644
--- a/contribs/gnogenesis/go.mod
+++ b/contribs/gnogenesis/go.mod
@@ -18,7 +18,6 @@ require (
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum
index 3c6127ac216..7ba3aede534 100644
--- a/contribs/gnogenesis/go.sum
+++ b/contribs/gnogenesis/go.sum
@@ -50,8 +50,6 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/contribs/gnohealth/go.mod b/contribs/gnohealth/go.mod
index e6d9f119c7b..4f5862a0d2e 100644
--- a/contribs/gnohealth/go.mod
+++ b/contribs/gnohealth/go.mod
@@ -12,7 +12,6 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
diff --git a/contribs/gnohealth/go.sum b/contribs/gnohealth/go.sum
index 116cfbff021..dd287d9ca84 100644
--- a/contribs/gnohealth/go.sum
+++ b/contribs/gnohealth/go.sum
@@ -45,8 +45,6 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod
index 0c794afd54c..479daed22f6 100644
--- a/contribs/gnokeykc/go.mod
+++ b/contribs/gnokeykc/go.mod
@@ -21,7 +21,6 @@ require (
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum
index 50eb5add218..cacf6788d45 100644
--- a/contribs/gnokeykc/go.sum
+++ b/contribs/gnokeykc/go.sum
@@ -54,8 +54,6 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod
index a81c2de4ba0..cd31adc4f6f 100644
--- a/contribs/gnomigrate/go.mod
+++ b/contribs/gnomigrate/go.mod
@@ -17,7 +17,6 @@ require (
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum
index 3c6127ac216..7ba3aede534 100644
--- a/contribs/gnomigrate/go.sum
+++ b/contribs/gnomigrate/go.sum
@@ -50,8 +50,6 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md
index 0e5ead338c5..b1791e65608 100644
--- a/docs/reference/stdlibs/std/chain.md
+++ b/docs/reference/stdlibs/std/chain.md
@@ -28,6 +28,18 @@ std.AssertOriginCall()
```
---
+## ChainDomain
+```go
+func ChainDomain() string
+```
+Returns the chain domain. Currently only `gno.land` is supported.
+
+#### Usage
+```go
+domain := std.ChainDomain() // gno.land
+```
+---
+
## Emit
```go
func Emit(typ string, attrs ...string)
diff --git a/examples/gno.land/p/demo/avl/pager/pager.gno b/examples/gno.land/p/demo/avl/pager/pager.gno
index 60bb44d97b6..cccdc0df645 100644
--- a/examples/gno.land/p/demo/avl/pager/pager.gno
+++ b/examples/gno.land/p/demo/avl/pager/pager.gno
@@ -15,6 +15,7 @@ type Pager struct {
PageQueryParam string
SizeQueryParam string
DefaultPageSize int
+ Reversed bool
}
// Page represents a single page of results.
@@ -36,12 +37,13 @@ type Item struct {
}
// NewPager creates a new Pager with default values.
-func NewPager(tree *avl.Tree, defaultPageSize int) *Pager {
+func NewPager(tree *avl.Tree, defaultPageSize int, reversed bool) *Pager {
return &Pager{
Tree: tree,
PageQueryParam: "page",
SizeQueryParam: "size",
DefaultPageSize: defaultPageSize,
+ Reversed: reversed,
}
}
@@ -86,10 +88,18 @@ func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page {
}
items := []Item{}
- p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool {
- items = append(items, Item{Key: key, Value: value})
- return false
- })
+
+ if p.Reversed {
+ p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool {
+ items = append(items, Item{Key: key, Value: value})
+ return false
+ })
+ } else {
+ p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool {
+ items = append(items, Item{Key: key, Value: value})
+ return false
+ })
+ }
page.Items = items
page.PageNumber = pageNumber
@@ -115,8 +125,8 @@ func (p *Pager) GetPageByPath(rawURL string) (*Page, error) {
return p.GetPageWithSize(pageNumber, pageSize), nil
}
-// UI generates the Markdown UI for the page selector.
-func (p *Page) Selector() string {
+// Picker generates the Markdown UI for the page Picker
+func (p *Page) Picker() string {
pageNumber := p.PageNumber
pageNumber = max(pageNumber, 1)
diff --git a/examples/gno.land/p/demo/avl/pager/pager_test.gno b/examples/gno.land/p/demo/avl/pager/pager_test.gno
index da4680db8c7..9869924e5b5 100644
--- a/examples/gno.land/p/demo/avl/pager/pager_test.gno
+++ b/examples/gno.land/p/demo/avl/pager/pager_test.gno
@@ -18,34 +18,67 @@ func TestPager_GetPage(t *testing.T) {
tree.Set("d", 4)
tree.Set("e", 5)
- // Create a new pager.
- pager := NewPager(tree, 10)
+ t.Run("normal ordering", func(t *testing.T) {
+ // Create a new pager.
+ pager := NewPager(tree, 10, false)
+
+ // Define test cases.
+ tests := []struct {
+ pageNumber int
+ pageSize int
+ expected []Item
+ }{
+ {1, 2, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}}},
+ {2, 2, []Item{{Key: "c", Value: 3}, {Key: "d", Value: 4}}},
+ {3, 2, []Item{{Key: "e", Value: 5}}},
+ {1, 3, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}, {Key: "c", Value: 3}}},
+ {2, 3, []Item{{Key: "d", Value: 4}, {Key: "e", Value: 5}}},
+ {1, 5, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}, {Key: "c", Value: 3}, {Key: "d", Value: 4}, {Key: "e", Value: 5}}},
+ {2, 5, []Item{}},
+ }
- // Define test cases.
- tests := []struct {
- pageNumber int
- pageSize int
- expected []Item
- }{
- {1, 2, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}}},
- {2, 2, []Item{{Key: "c", Value: 3}, {Key: "d", Value: 4}}},
- {3, 2, []Item{{Key: "e", Value: 5}}},
- {1, 3, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}, {Key: "c", Value: 3}}},
- {2, 3, []Item{{Key: "d", Value: 4}, {Key: "e", Value: 5}}},
- {1, 5, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}, {Key: "c", Value: 3}, {Key: "d", Value: 4}, {Key: "e", Value: 5}}},
- {2, 5, []Item{}},
- }
+ for _, tt := range tests {
+ page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize)
- for _, tt := range tests {
- page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize)
+ uassert.Equal(t, len(tt.expected), len(page.Items))
- uassert.Equal(t, len(tt.expected), len(page.Items))
+ for i, item := range page.Items {
+ uassert.Equal(t, tt.expected[i].Key, item.Key)
+ uassert.Equal(t, tt.expected[i].Value, item.Value)
+ }
+ }
+ })
+
+ t.Run("reversed ordering", func(t *testing.T) {
+ // Create a new pager.
+ pager := NewPager(tree, 10, true)
+
+ // Define test cases.
+ tests := []struct {
+ pageNumber int
+ pageSize int
+ expected []Item
+ }{
+ {1, 2, []Item{{Key: "e", Value: 5}, {Key: "d", Value: 4}}},
+ {2, 2, []Item{{Key: "c", Value: 3}, {Key: "b", Value: 2}}},
+ {3, 2, []Item{{Key: "a", Value: 1}}},
+ {1, 3, []Item{{Key: "e", Value: 5}, {Key: "d", Value: 4}, {Key: "c", Value: 3}}},
+ {2, 3, []Item{{Key: "b", Value: 2}, {Key: "a", Value: 1}}},
+ {1, 5, []Item{{Key: "e", Value: 5}, {Key: "d", Value: 4}, {Key: "c", Value: 3}, {Key: "b", Value: 2}, {Key: "a", Value: 1}}},
+ {2, 5, []Item{}},
+ }
- for i, item := range page.Items {
- uassert.Equal(t, tt.expected[i].Key, item.Key)
- uassert.Equal(t, tt.expected[i].Value, item.Value)
+ for _, tt := range tests {
+ page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize)
+
+ uassert.Equal(t, len(tt.expected), len(page.Items))
+
+ for i, item := range page.Items {
+ uassert.Equal(t, tt.expected[i].Key, item.Key)
+ uassert.Equal(t, tt.expected[i].Value, item.Value)
+ }
}
- }
+ })
}
func TestPager_GetPageByPath(t *testing.T) {
@@ -56,7 +89,7 @@ func TestPager_GetPageByPath(t *testing.T) {
}
// Create a new pager.
- pager := NewPager(tree, 10)
+ pager := NewPager(tree, 10, false)
// Define test cases.
tests := []struct {
@@ -80,7 +113,7 @@ func TestPager_GetPageByPath(t *testing.T) {
}
}
-func TestPage_Selector(t *testing.T) {
+func TestPage_Picker(t *testing.T) {
// Create a new AVL tree and populate it with some key-value pairs.
tree := avl.NewTree()
tree.Set("a", 1)
@@ -90,7 +123,7 @@ func TestPage_Selector(t *testing.T) {
tree.Set("e", 5)
// Create a new pager.
- pager := NewPager(tree, 10)
+ pager := NewPager(tree, 10, false)
// Define test cases.
tests := []struct {
@@ -106,7 +139,7 @@ func TestPage_Selector(t *testing.T) {
for _, tt := range tests {
page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize)
- ui := page.Selector()
+ ui := page.Picker()
uassert.Equal(t, tt.expected, ui)
}
}
@@ -119,7 +152,7 @@ func TestPager_UI_WithManyPages(t *testing.T) {
}
// Create a new pager.
- pager := NewPager(tree, 10)
+ pager := NewPager(tree, 10, false)
// Define test cases for a large number of pages.
tests := []struct {
@@ -145,7 +178,7 @@ func TestPager_UI_WithManyPages(t *testing.T) {
for _, tt := range tests {
page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize)
- ui := page.Selector()
+ ui := page.Picker()
uassert.Equal(t, tt.expected, ui)
}
}
@@ -160,7 +193,7 @@ func TestPager_ParseQuery(t *testing.T) {
tree.Set("e", 5)
// Create a new pager.
- pager := NewPager(tree, 10)
+ pager := NewPager(tree, 10, false)
// Define test cases.
tests := []struct {
diff --git a/examples/gno.land/p/demo/avl/pager/z_filetest.gno b/examples/gno.land/p/demo/avl/pager/z_filetest.gno
index 17029f57861..6342888d6b4 100644
--- a/examples/gno.land/p/demo/avl/pager/z_filetest.gno
+++ b/examples/gno.land/p/demo/avl/pager/z_filetest.gno
@@ -16,7 +16,7 @@ func main() {
}
// Create a new pager.
- pager := pager.NewPager(tree, 7)
+ pager := pager.NewPager(tree, 7, false)
for pn := -1; pn < 8; pn++ {
page := pager.GetPage(pn)
@@ -25,7 +25,7 @@ func main() {
for idx, item := range page.Items {
println(ufmt.Sprintf("- idx=%d key=%s value=%d", idx, item.Key, item.Value))
}
- println(page.Selector())
+ println(page.Picker())
println()
}
}
diff --git a/examples/gno.land/p/moul/debug/debug.gno b/examples/gno.land/p/moul/debug/debug.gno
new file mode 100644
index 00000000000..9ba3dd36a98
--- /dev/null
+++ b/examples/gno.land/p/moul/debug/debug.gno
@@ -0,0 +1,92 @@
+// Package debug provides utilities for logging and displaying debug information
+// within Gno realms. It supports conditional rendering of logs and metadata,
+// toggleable via query parameters.
+//
+// Key Features:
+// - Log collection and display using Markdown formatting.
+// - Metadata display for realm path, address, and height.
+// - Collapsible debug section for cleaner presentation.
+// - Query-based debug toggle using `?debug=1`.
+package debug
+
+import (
+ "std"
+ "time"
+
+ "gno.land/p/demo/ufmt"
+ "gno.land/p/moul/md"
+ "gno.land/p/moul/mdtable"
+ "gno.land/p/moul/realmpath"
+)
+
+// Debug encapsulates debug information, including logs and metadata.
+type Debug struct {
+ Logs []string
+ HideMetadata bool
+}
+
+// Log appends a new line of debug information to the Logs slice.
+func (d *Debug) Log(line string) {
+ d.Logs = append(d.Logs, line)
+}
+
+// Render generates the debug content as a collapsible Markdown section.
+// It conditionally renders logs and metadata if enabled via the `?debug=1` query parameter.
+func (d Debug) Render(path string) string {
+ if realmpath.Parse(path).Query.Get("debug") != "1" {
+ return ""
+ }
+
+ var content string
+
+ if d.Logs != nil {
+ content += md.H3("Logs")
+ content += md.BulletList(d.Logs)
+ }
+
+ if !d.HideMetadata {
+ content += md.H3("Metadata")
+ table := mdtable.Table{
+ Headers: []string{"Key", "Value"},
+ }
+ table.Append([]string{"`std.CurrentRealm().PkgPath()`", string(std.CurrentRealm().PkgPath())})
+ table.Append([]string{"`std.CurrentRealm().Addr()`", string(std.CurrentRealm().Addr())})
+ table.Append([]string{"`std.PrevRealm().PkgPath()`", string(std.PrevRealm().PkgPath())})
+ table.Append([]string{"`std.PrevRealm().Addr()`", string(std.PrevRealm().Addr())})
+ table.Append([]string{"`std.GetHeight()`", ufmt.Sprintf("%d", std.GetHeight())})
+ table.Append([]string{"`time.Now().Format(time.RFC3339)`", time.Now().Format(time.RFC3339)})
+ content += table.String()
+ }
+
+ if content == "" {
+ return ""
+ }
+
+ return md.CollapsibleSection("debug", content)
+}
+
+// Render displays metadata about the current realm but does not display logs.
+// This function uses a default Debug struct with metadata enabled and no logs.
+func Render(path string) string {
+ return Debug{}.Render(path)
+}
+
+// IsEnabled checks if the `?debug=1` query parameter is set in the given path.
+// Returns true if debugging is enabled, otherwise false.
+func IsEnabled(path string) bool {
+ req := realmpath.Parse(path)
+ return req.Query.Get("debug") == "1"
+}
+
+// ToggleURL modifies the given path's query string to toggle the `?debug=1` parameter.
+// If debugging is currently enabled, it removes the parameter.
+// If debugging is disabled, it adds the parameter.
+func ToggleURL(path string) string {
+ req := realmpath.Parse(path)
+ if IsEnabled(path) {
+ req.Query.Del("debug")
+ } else {
+ req.Query.Add("debug", "1")
+ }
+ return req.String()
+}
diff --git a/examples/gno.land/p/moul/debug/gno.mod b/examples/gno.land/p/moul/debug/gno.mod
new file mode 100644
index 00000000000..eb48ed292ca
--- /dev/null
+++ b/examples/gno.land/p/moul/debug/gno.mod
@@ -0,0 +1 @@
+module gno.land/p/moul/debug
diff --git a/examples/gno.land/p/moul/debug/z1_filetest.gno b/examples/gno.land/p/moul/debug/z1_filetest.gno
new file mode 100644
index 00000000000..8203749d3c7
--- /dev/null
+++ b/examples/gno.land/p/moul/debug/z1_filetest.gno
@@ -0,0 +1,31 @@
+package main
+
+import "gno.land/p/moul/debug"
+
+func main() {
+ println("---")
+ println(debug.Render(""))
+ println("---")
+ println(debug.Render("?debug=1"))
+ println("---")
+}
+
+// Output:
+// ---
+//
+// ---
+// debug
+//
+// ### Metadata
+// | Key | Value |
+// | --- | --- |
+// | `std.CurrentRealm().PkgPath()` | |
+// | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+// | `std.PrevRealm().PkgPath()` | |
+// | `std.PrevRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+// | `std.GetHeight()` | 123 |
+// | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z |
+//
+//
+//
+// ---
diff --git a/examples/gno.land/p/moul/debug/z2_filetest.gno b/examples/gno.land/p/moul/debug/z2_filetest.gno
new file mode 100644
index 00000000000..32c2fe49951
--- /dev/null
+++ b/examples/gno.land/p/moul/debug/z2_filetest.gno
@@ -0,0 +1,37 @@
+package main
+
+import "gno.land/p/moul/debug"
+
+func main() {
+ var d debug.Debug
+ d.Log("hello world!")
+ d.Log("foobar")
+ println("---")
+ println(d.Render(""))
+ println("---")
+ println(d.Render("?debug=1"))
+ println("---")
+}
+
+// Output:
+// ---
+//
+// ---
+// debug
+//
+// ### Logs
+// - hello world!
+// - foobar
+// ### Metadata
+// | Key | Value |
+// | --- | --- |
+// | `std.CurrentRealm().PkgPath()` | |
+// | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+// | `std.PrevRealm().PkgPath()` | |
+// | `std.PrevRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm |
+// | `std.GetHeight()` | 123 |
+// | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z |
+//
+//
+//
+// ---
diff --git a/examples/gno.land/p/moul/md/gno.mod b/examples/gno.land/p/moul/md/gno.mod
new file mode 100644
index 00000000000..55d124d9e6b
--- /dev/null
+++ b/examples/gno.land/p/moul/md/gno.mod
@@ -0,0 +1 @@
+module gno.land/p/moul/md
diff --git a/examples/gno.land/p/moul/md/md.gno b/examples/gno.land/p/moul/md/md.gno
new file mode 100644
index 00000000000..61d6948b997
--- /dev/null
+++ b/examples/gno.land/p/moul/md/md.gno
@@ -0,0 +1,242 @@
+// Package md provides helper functions for generating Markdown content programmatically.
+//
+// It includes utilities for text formatting, creating lists, blockquotes, code blocks,
+// links, images, and more.
+//
+// Highlights:
+// - Supports basic Markdown syntax such as bold, italic, strikethrough, headers, and lists.
+// - Manages multiline support in lists (e.g., bullet, ordered, and todo lists).
+// - Includes advanced helpers like inline images with links and nested list prefixes.
+package md
+
+import (
+ "strconv"
+ "strings"
+)
+
+// Bold returns bold text for markdown.
+// Example: Bold("foo") => "**foo**"
+func Bold(text string) string {
+ return "**" + text + "**"
+}
+
+// Italic returns italicized text for markdown.
+// Example: Italic("foo") => "*foo*"
+func Italic(text string) string {
+ return "*" + text + "*"
+}
+
+// Strikethrough returns strikethrough text for markdown.
+// Example: Strikethrough("foo") => "~~foo~~"
+func Strikethrough(text string) string {
+ return "~~" + text + "~~"
+}
+
+// H1 returns a level 1 header for markdown.
+// Example: H1("foo") => "# foo\n"
+func H1(text string) string {
+ return "# " + text + "\n"
+}
+
+// H2 returns a level 2 header for markdown.
+// Example: H2("foo") => "## foo\n"
+func H2(text string) string {
+ return "## " + text + "\n"
+}
+
+// H3 returns a level 3 header for markdown.
+// Example: H3("foo") => "### foo\n"
+func H3(text string) string {
+ return "### " + text + "\n"
+}
+
+// H4 returns a level 4 header for markdown.
+// Example: H4("foo") => "#### foo\n"
+func H4(text string) string {
+ return "#### " + text + "\n"
+}
+
+// H5 returns a level 5 header for markdown.
+// Example: H5("foo") => "##### foo\n"
+func H5(text string) string {
+ return "##### " + text + "\n"
+}
+
+// H6 returns a level 6 header for markdown.
+// Example: H6("foo") => "###### foo\n"
+func H6(text string) string {
+ return "###### " + text + "\n"
+}
+
+// BulletList returns a bullet list for markdown.
+// Example: BulletList([]string{"foo", "bar"}) => "- foo\n- bar\n"
+func BulletList(items []string) string {
+ var sb strings.Builder
+ for _, item := range items {
+ sb.WriteString(BulletItem(item))
+ }
+ return sb.String()
+}
+
+// BulletItem returns a bullet item for markdown.
+// Example: BulletItem("foo") => "- foo\n"
+func BulletItem(item string) string {
+ var sb strings.Builder
+ lines := strings.Split(item, "\n")
+ sb.WriteString("- " + lines[0] + "\n")
+ for _, line := range lines[1:] {
+ sb.WriteString(" " + line + "\n")
+ }
+ return sb.String()
+}
+
+// OrderedList returns an ordered list for markdown.
+// Example: OrderedList([]string{"foo", "bar"}) => "1. foo\n2. bar\n"
+func OrderedList(items []string) string {
+ var sb strings.Builder
+ for i, item := range items {
+ lines := strings.Split(item, "\n")
+ sb.WriteString(strconv.Itoa(i+1) + ". " + lines[0] + "\n")
+ for _, line := range lines[1:] {
+ sb.WriteString(" " + line + "\n")
+ }
+ }
+ return sb.String()
+}
+
+// TodoList returns a list of todo items with checkboxes for markdown.
+// Example: TodoList([]string{"foo", "bar\nmore bar"}, []bool{true, false}) => "- [x] foo\n- [ ] bar\n more bar\n"
+func TodoList(items []string, done []bool) string {
+ var sb strings.Builder
+ for i, item := range items {
+ sb.WriteString(TodoItem(item, done[i]))
+ }
+ return sb.String()
+}
+
+// TodoItem returns a todo item with checkbox for markdown.
+// Example: TodoItem("foo", true) => "- [x] foo\n"
+func TodoItem(item string, done bool) string {
+ var sb strings.Builder
+ checkbox := " "
+ if done {
+ checkbox = "x"
+ }
+ lines := strings.Split(item, "\n")
+ sb.WriteString("- [" + checkbox + "] " + lines[0] + "\n")
+ for _, line := range lines[1:] {
+ sb.WriteString(" " + line + "\n")
+ }
+ return sb.String()
+}
+
+// Nested prefixes each line with a given prefix, enabling nested lists.
+// Example: Nested("- foo\n- bar", " ") => " - foo\n - bar\n"
+func Nested(content, prefix string) string {
+ lines := strings.Split(content, "\n")
+ for i := range lines {
+ if strings.TrimSpace(lines[i]) != "" {
+ lines[i] = prefix + lines[i]
+ }
+ }
+ return strings.Join(lines, "\n")
+}
+
+// Blockquote returns a blockquote for markdown.
+// Example: Blockquote("foo\nbar") => "> foo\n> bar\n"
+func Blockquote(text string) string {
+ lines := strings.Split(text, "\n")
+ var sb strings.Builder
+ for _, line := range lines {
+ sb.WriteString("> " + line + "\n")
+ }
+ return sb.String()
+}
+
+// InlineCode returns inline code for markdown.
+// Example: InlineCode("foo") => "`foo`"
+func InlineCode(code string) string {
+ return "`" + strings.ReplaceAll(code, "`", "\\`") + "`"
+}
+
+// CodeBlock creates a markdown code block.
+// Example: CodeBlock("foo") => "```\nfoo\n```"
+func CodeBlock(content string) string {
+ return "```\n" + strings.ReplaceAll(content, "```", "\\```") + "\n```"
+}
+
+// LanguageCodeBlock creates a markdown code block with language-specific syntax highlighting.
+// Example: LanguageCodeBlock("go", "foo") => "```go\nfoo\n```"
+func LanguageCodeBlock(language, content string) string {
+ return "```" + language + "\n" + strings.ReplaceAll(content, "```", "\\```") + "\n```"
+}
+
+// HorizontalRule returns a horizontal rule for markdown.
+// Example: HorizontalRule() => "---\n"
+func HorizontalRule() string {
+ return "---\n"
+}
+
+// Link returns a hyperlink for markdown.
+// Example: Link("foo", "http://example.com") => "[foo](http://example.com)"
+func Link(text, url string) string {
+ return "[" + EscapeText(text) + "](" + url + ")"
+}
+
+// InlineImageWithLink creates an inline image wrapped in a hyperlink for markdown.
+// Example: InlineImageWithLink("alt text", "image-url", "link-url") => "[![alt text](image-url)](link-url)"
+func InlineImageWithLink(altText, imageUrl, linkUrl string) string {
+ return "[" + Image(altText, imageUrl) + "](" + linkUrl + ")"
+}
+
+// Image returns an image for markdown.
+// Example: Image("foo", "http://example.com") => "![foo](http://example.com)"
+func Image(altText, url string) string {
+ return "![" + EscapeText(altText) + "](" + url + ")"
+}
+
+// Footnote returns a footnote for markdown.
+// Example: Footnote("foo", "bar") => "[foo]: bar"
+func Footnote(reference, text string) string {
+ return "[" + EscapeText(reference) + "]: " + text
+}
+
+// Paragraph wraps the given text in a Markdown paragraph.
+// Example: Paragraph("foo") => "foo\n"
+func Paragraph(content string) string {
+ return content + "\n\n"
+}
+
+// CollapsibleSection creates a collapsible section for markdown using
+// HTML and tags.
+// Example:
+// CollapsibleSection("Click to expand", "Hidden content")
+// =>
+// Click to expand
+//
+// Hidden content
+//
+func CollapsibleSection(title, content string) string {
+ return "" + EscapeText(title) + "\n\n" + content + "\n\n"
+}
+
+// EscapeText escapes special Markdown characters in regular text where needed.
+func EscapeText(text string) string {
+ replacer := strings.NewReplacer(
+ `*`, `\*`,
+ `_`, `\_`,
+ `[`, `\[`,
+ `]`, `\]`,
+ `(`, `\(`,
+ `)`, `\)`,
+ `~`, `\~`,
+ `>`, `\>`,
+ `|`, `\|`,
+ `-`, `\-`,
+ `+`, `\+`,
+ ".", `\.`,
+ "!", `\!`,
+ "`", "\\`",
+ )
+ return replacer.Replace(text)
+}
diff --git a/examples/gno.land/p/moul/md/md_test.gno b/examples/gno.land/p/moul/md/md_test.gno
new file mode 100644
index 00000000000..144ae58d918
--- /dev/null
+++ b/examples/gno.land/p/moul/md/md_test.gno
@@ -0,0 +1,88 @@
+package md
+
+import (
+ "testing"
+
+ "gno.land/p/moul/md"
+)
+
+func TestHelpers(t *testing.T) {
+ tests := []struct {
+ name string
+ function func() string
+ expected string
+ }{
+ {"Bold", func() string { return md.Bold("foo") }, "**foo**"},
+ {"Italic", func() string { return md.Italic("foo") }, "*foo*"},
+ {"Strikethrough", func() string { return md.Strikethrough("foo") }, "~~foo~~"},
+ {"H1", func() string { return md.H1("foo") }, "# foo\n"},
+ {"HorizontalRule", md.HorizontalRule, "---\n"},
+ {"InlineCode", func() string { return md.InlineCode("foo") }, "`foo`"},
+ {"CodeBlock", func() string { return md.CodeBlock("foo") }, "```\nfoo\n```"},
+ {"LanguageCodeBlock", func() string { return md.LanguageCodeBlock("go", "foo") }, "```go\nfoo\n```"},
+ {"Link", func() string { return md.Link("foo", "http://example.com") }, "[foo](http://example.com)"},
+ {"Image", func() string { return md.Image("foo", "http://example.com") }, "![foo](http://example.com)"},
+ {"InlineImageWithLink", func() string { return md.InlineImageWithLink("alt", "image-url", "link-url") }, "[![alt](image-url)](link-url)"},
+ {"Footnote", func() string { return md.Footnote("foo", "bar") }, "[foo]: bar"},
+ {"Paragraph", func() string { return md.Paragraph("foo") }, "foo\n\n"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := tt.function()
+ if result != tt.expected {
+ t.Errorf("%s() = %q, want %q", tt.name, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestLists(t *testing.T) {
+ t.Run("BulletList", func(t *testing.T) {
+ items := []string{"foo", "bar"}
+ expected := "- foo\n- bar\n"
+ result := md.BulletList(items)
+ if result != expected {
+ t.Errorf("BulletList(%q) = %q, want %q", items, result, expected)
+ }
+ })
+
+ t.Run("OrderedList", func(t *testing.T) {
+ items := []string{"foo", "bar"}
+ expected := "1. foo\n2. bar\n"
+ result := md.OrderedList(items)
+ if result != expected {
+ t.Errorf("OrderedList(%q) = %q, want %q", items, result, expected)
+ }
+ })
+
+ t.Run("TodoList", func(t *testing.T) {
+ items := []string{"foo", "bar\nmore bar"}
+ done := []bool{true, false}
+ expected := "- [x] foo\n- [ ] bar\n more bar\n"
+ result := md.TodoList(items, done)
+ if result != expected {
+ t.Errorf("TodoList(%q, %q) = %q, want %q", items, done, result, expected)
+ }
+ })
+}
+
+func TestNested(t *testing.T) {
+ t.Run("Nested Single Level", func(t *testing.T) {
+ content := "- foo\n- bar"
+ expected := " - foo\n - bar"
+ result := md.Nested(content, " ")
+ if result != expected {
+ t.Errorf("Nested(%q) = %q, want %q", content, result, expected)
+ }
+ })
+
+ t.Run("Nested Double Level", func(t *testing.T) {
+ content := " - foo\n - bar"
+ expected := " - foo\n - bar"
+ result := md.Nested(content, " ")
+ if result != expected {
+ t.Errorf("Nested(%q) = %q, want %q", content, result, expected)
+ }
+ })
+}
diff --git a/examples/gno.land/p/moul/md/z1_filetest.gno b/examples/gno.land/p/moul/md/z1_filetest.gno
new file mode 100644
index 00000000000..077e1732bcb
--- /dev/null
+++ b/examples/gno.land/p/moul/md/z1_filetest.gno
@@ -0,0 +1,87 @@
+package main
+
+import "gno.land/p/moul/md"
+
+func main() {
+ println(md.H1("Header 1"))
+ println(md.H2("Header 2"))
+ println(md.H3("Header 3"))
+ println(md.H4("Header 4"))
+ println(md.H5("Header 5"))
+ println(md.H6("Header 6"))
+ println(md.Bold("bold"))
+ println(md.Italic("italic"))
+ println(md.Strikethrough("strikethrough"))
+ println(md.BulletList([]string{
+ "Item 1",
+ "Item 2\nMore details for item 2",
+ }))
+ println(md.OrderedList([]string{"Step 1", "Step 2"}))
+ println(md.TodoList([]string{"Task 1", "Task 2\nSubtask 2"}, []bool{true, false}))
+ println(md.Nested(md.BulletList([]string{"Parent Item", md.OrderedList([]string{"Child 1", "Child 2"})}), " "))
+ println(md.Blockquote("This is a blockquote\nSpanning multiple lines"))
+ println(md.InlineCode("inline `code`"))
+ println(md.CodeBlock("line1\nline2"))
+ println(md.LanguageCodeBlock("go", "func main() {\nprintln(\"Hello, world!\")\n}"))
+ println(md.HorizontalRule())
+ println(md.Link("Gno", "http://gno.land"))
+ println(md.Image("Alt Text", "http://example.com/image.png"))
+ println(md.InlineImageWithLink("Alt Text", "http://example.com/image.png", "http://example.com"))
+ println(md.Footnote("ref", "This is a footnote"))
+ println(md.Paragraph("This is a paragraph."))
+}
+
+// Output:
+// # Header 1
+//
+// ## Header 2
+//
+// ### Header 3
+//
+// #### Header 4
+//
+// ##### Header 5
+//
+// ###### Header 6
+//
+// **bold**
+// *italic*
+// ~~strikethrough~~
+// - Item 1
+// - Item 2
+// More details for item 2
+//
+// 1. Step 1
+// 2. Step 2
+//
+// - [x] Task 1
+// - [ ] Task 2
+// Subtask 2
+//
+// - Parent Item
+// - 1. Child 1
+// 2. Child 2
+//
+//
+// > This is a blockquote
+// > Spanning multiple lines
+//
+// `inline \`code\``
+// ```
+// line1
+// line2
+// ```
+// ```go
+// func main() {
+// println("Hello, world!")
+// }
+// ```
+// ---
+//
+// [Gno](http://gno.land)
+// ![Alt Text](http://example.com/image.png)
+// [![Alt Text](http://example.com/image.png)](http://example.com)
+// [ref]: This is a footnote
+// This is a paragraph.
+//
+//
diff --git a/examples/gno.land/p/moul/web25/gno.mod b/examples/gno.land/p/moul/web25/gno.mod
new file mode 100644
index 00000000000..f27bc793bf7
--- /dev/null
+++ b/examples/gno.land/p/moul/web25/gno.mod
@@ -0,0 +1 @@
+module gno.land/p/moul/web25
diff --git a/examples/gno.land/p/moul/web25/web25.gno b/examples/gno.land/p/moul/web25/web25.gno
new file mode 100644
index 00000000000..46d564b70ad
--- /dev/null
+++ b/examples/gno.land/p/moul/web25/web25.gno
@@ -0,0 +1,51 @@
+// Pacakge web25 provides an opinionated way to register an external web2
+// frontend to provide a "better" web2.5 experience.
+package web25
+
+import (
+ "strings"
+
+ "gno.land/p/moul/realmpath"
+)
+
+type Config struct {
+ CID string
+ URL string
+ Text string
+}
+
+func (c *Config) SetRemoteFrontendByURL(url string) {
+ c.CID = ""
+ c.URL = url
+}
+
+func (c *Config) SetRemoteFrontendByCID(cid string) {
+ c.CID = cid
+ c.URL = ""
+}
+
+func (c Config) GetLink() string {
+ if c.CID != "" {
+ return "https://ipfs.io/ipfs/" + c.CID
+ }
+ return c.URL
+}
+
+const DefaultText = "Click [here]({link}) to visit the full rendering experience.\n"
+
+// Render displays a frontend link at the top of your realm's Render function in
+// a concistent way to help gno visitors to have a consistent experience.
+//
+// if query is not nil, then it will check if it's not disable by ?no-web25, so
+// that you can call the render function from an external point of view.
+func (c Config) Render(path string) string {
+ if realmpath.Parse(path).Query.Get("no-web25") == "1" {
+ return ""
+ }
+ text := c.Text
+ if text == "" {
+ text = DefaultText
+ }
+ text = strings.ReplaceAll(text, "{link}", c.GetLink())
+ return text
+}
diff --git a/examples/gno.land/p/moul/web25/web25_test.gno b/examples/gno.land/p/moul/web25/web25_test.gno
new file mode 100644
index 00000000000..6d58a586595
--- /dev/null
+++ b/examples/gno.land/p/moul/web25/web25_test.gno
@@ -0,0 +1 @@
+package web25
diff --git a/examples/gno.land/r/demo/userbook/render.gno b/examples/gno.land/r/demo/userbook/render.gno
index 22d7f97eabd..94f7567cbf4 100644
--- a/examples/gno.land/r/demo/userbook/render.gno
+++ b/examples/gno.land/r/demo/userbook/render.gno
@@ -2,16 +2,19 @@
package userbook
import (
- "sort"
"strconv"
+ "gno.land/r/demo/users"
+
"gno.land/p/demo/avl/pager"
"gno.land/p/demo/ufmt"
"gno.land/p/moul/txlink"
)
+const usersLink = "/r/demo/users"
+
func Render(path string) string {
- p := pager.NewPager(signupsTree, 2)
+ p := pager.NewPager(signupsTree, 20, true)
page := p.MustGetPageByPath(path)
out := "# Welcome to UserBook!\n\n"
@@ -19,33 +22,19 @@ func Render(path string) string {
out += ufmt.Sprintf("## [Click here to sign up!](%s)\n\n", txlink.Call("SignUp"))
out += "---\n\n"
- var sorted sortedSignups
for _, item := range page.Items {
- sorted = append(sorted, item.Value.(*Signup))
- }
+ signup := item.Value.(*Signup)
+ user := signup.address.String()
- sort.Sort(sorted)
+ if data := users.GetUserByAddress(signup.address); data != nil {
+ user = ufmt.Sprintf("[%s](%s:%s)", data.Name, usersLink, data.Name)
+ }
- for _, item := range sorted {
- out += ufmt.Sprintf("- **User #%d - %s - signed up on %s**\n\n", item.ordinal, item.address.String(), item.timestamp.Format("02-01-2006 15:04:05"))
+ out += ufmt.Sprintf("- **User #%d - %s - signed up on %s**\n\n", signup.ordinal, user, signup.timestamp.Format("January 2 2006, 03:04:04 PM"))
}
out += "---\n\n"
out += "**Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "**\n\n"
- out += page.Selector() // Repeat selector for ease of navigation
+ out += page.Picker()
return out
}
-
-type sortedSignups []*Signup
-
-func (s sortedSignups) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
-}
-
-func (s sortedSignups) Len() int {
- return len(s)
-}
-
-func (s sortedSignups) Less(i, j int) bool {
- return s[i].timestamp.Before(s[j].timestamp)
-}
diff --git a/examples/gno.land/r/demo/userbook/userbook.gno b/examples/gno.land/r/demo/userbook/userbook.gno
index c958dc9e5b0..03027f064b0 100644
--- a/examples/gno.land/r/demo/userbook/userbook.gno
+++ b/examples/gno.land/r/demo/userbook/userbook.gno
@@ -6,6 +6,7 @@ import (
"time"
"gno.land/p/demo/avl"
+ "gno.land/p/demo/seqid"
"gno.land/p/demo/ufmt"
)
@@ -15,7 +16,11 @@ type Signup struct {
timestamp time.Time
}
-var signupsTree = avl.NewTree()
+var (
+ signupsTree = avl.NewTree()
+ tracker = avl.NewTree()
+ idCounter seqid.ID
+)
const signUpEvent = "SignUp"
@@ -28,19 +33,22 @@ func SignUp() string {
caller := std.PrevRealm().Addr()
// Check if the user is already signed up
- if _, exists := signupsTree.Get(caller.String()); exists {
- panic(caller + " is already signed up!")
+ if _, exists := tracker.Get(caller.String()); exists {
+ panic(caller.String() + " is already signed up!")
}
now := time.Now()
+
// Sign up the user
- signupsTree.Set(caller.String(), &Signup{
- std.PrevRealm().Addr(),
+ signupsTree.Set(idCounter.Next().String(), &Signup{
+ caller,
signupsTree.Size(),
now,
})
- std.Emit(signUpEvent, "SignedUpAccount", caller.String())
+ tracker.Set(caller.String(), struct{}{})
+
+ std.Emit(signUpEvent, "account", caller.String())
return ufmt.Sprintf("%s added to userbook! Timestamp: %s", caller.String(), now.Format(time.RFC822Z))
}
diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno
index 1f08c9ae08c..8547a6e60e0 100644
--- a/examples/gno.land/r/demo/users/users.gno
+++ b/examples/gno.land/r/demo/users/users.gno
@@ -328,14 +328,14 @@ func Render(fullPath string) string {
func renderHome(path string) string {
doc := ""
- page := pager.NewPager(&name2User, 50).MustGetPageByPath(path)
+ page := pager.NewPager(&name2User, 50, false).MustGetPageByPath(path)
for _, item := range page.Items {
user := item.Value.(*users.User)
doc += " * [" + user.Name + "](/r/demo/users:" + user.Name + ")\n"
}
doc += "\n"
- doc += page.Selector()
+ doc += page.Picker()
return doc
}
diff --git a/examples/gno.land/r/docs/avl_pager/avl_pager.gno b/examples/gno.land/r/docs/avl_pager/avl_pager.gno
index 75807b71981..af8a6a10b48 100644
--- a/examples/gno.land/r/docs/avl_pager/avl_pager.gno
+++ b/examples/gno.land/r/docs/avl_pager/avl_pager.gno
@@ -22,19 +22,19 @@ func init() {
// Render paginated content based on the given URL path.
// URL format: `...?page=&size=` (default is page 1 and size 10).
func Render(path string) string {
- p := pager.NewPager(tree, 10) // Default page size is 10
+ p := pager.NewPager(tree, 10, false) // Default page size is 10
page := p.MustGetPageByPath(path)
// Header and pagination info
result := "# Paginated Items\n"
result += "Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "\n\n"
- result += page.Selector() + "\n\n"
+ result += page.Picker() + "\n\n"
// Display items on the current page
for _, item := range page.Items {
result += "- " + item.Key + ": " + item.Value.(string) + "\n"
}
- result += "\n" + page.Selector() // Repeat selector for ease of navigation
+ result += "\n" + page.Picker() // Repeat page picker for ease of navigation
return result
}
diff --git a/examples/gno.land/r/leon/hof/render.gno b/examples/gno.land/r/leon/hof/render.gno
index b4d51d03362..0721c7d6e72 100644
--- a/examples/gno.land/r/leon/hof/render.gno
+++ b/examples/gno.land/r/leon/hof/render.gno
@@ -38,7 +38,7 @@ func (e Exhibition) Render(path string, dashboard bool) string {
out += "
\n\n"
- page := pager.NewPager(e.itemsSorted, pageSize).MustGetPageByPath(path)
+ page := pager.NewPager(e.itemsSorted, pageSize, false).MustGetPageByPath(path)
for i := len(page.Items) - 1; i >= 0; i-- {
item := page.Items[i]
@@ -52,7 +52,7 @@ func (e Exhibition) Render(path string, dashboard bool) string {
out += "
\n\n"
- out += page.Selector()
+ out += page.Picker()
return out
}
diff --git a/examples/gno.land/r/matijamarjanovic/home/config.gno b/examples/gno.land/r/matijamarjanovic/home/config.gno
new file mode 100644
index 00000000000..2a9669c0b58
--- /dev/null
+++ b/examples/gno.land/r/matijamarjanovic/home/config.gno
@@ -0,0 +1,64 @@
+package home
+
+import (
+ "errors"
+ "std"
+)
+
+var (
+ mainAddr = std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y") // matija's main address
+ backupAddr std.Address // backup address
+
+ errorInvalidAddr = errors.New("config: invalid address")
+ errorUnauthorized = errors.New("config: unauthorized")
+)
+
+func Address() std.Address {
+ return mainAddr
+}
+
+func Backup() std.Address {
+ return backupAddr
+}
+
+func SetAddress(newAddress std.Address) error {
+ if !newAddress.IsValid() {
+ return errorInvalidAddr
+ }
+
+ if err := checkAuthorized(); err != nil {
+ return err
+ }
+
+ mainAddr = newAddress
+ return nil
+}
+
+func SetBackup(newAddress std.Address) error {
+ if !newAddress.IsValid() {
+ return errorInvalidAddr
+ }
+
+ if err := checkAuthorized(); err != nil {
+ return err
+ }
+
+ backupAddr = newAddress
+ return nil
+}
+
+func checkAuthorized() error {
+ caller := std.GetOrigCaller()
+ if caller != mainAddr && caller != backupAddr {
+ return errorUnauthorized
+ }
+
+ return nil
+}
+
+func AssertAuthorized() {
+ caller := std.GetOrigCaller()
+ if caller != mainAddr && caller != backupAddr {
+ panic(errorUnauthorized)
+ }
+}
diff --git a/examples/gno.land/r/matijamarjanovic/home/gno.mod b/examples/gno.land/r/matijamarjanovic/home/gno.mod
new file mode 100644
index 00000000000..0457c947c01
--- /dev/null
+++ b/examples/gno.land/r/matijamarjanovic/home/gno.mod
@@ -0,0 +1 @@
+module gno.land/r/matijamarjanovic/home
diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno
new file mode 100644
index 00000000000..3757324108a
--- /dev/null
+++ b/examples/gno.land/r/matijamarjanovic/home/home.gno
@@ -0,0 +1,238 @@
+package home
+
+import (
+ "std"
+ "strings"
+
+ "gno.land/p/demo/ufmt"
+ "gno.land/p/moul/md"
+ "gno.land/r/leon/hof"
+)
+
+var (
+ pfp string // link to profile picture
+ pfpCaption string // profile picture caption
+ abtMe string
+
+ modernVotes int64
+ classicVotes int64
+ minimalVotes int64
+ currentTheme string
+
+ modernLink string
+ classicLink string
+ minimalLink string
+)
+
+func init() {
+ pfp = "https://static.artzone.ai/media/38734/conversions/IPF9dR7ro7n05CmMLLrXIojycr1qdLFxgutaaanG-w768.webp"
+ pfpCaption = "My profile picture - Tarantula Nebula"
+ abtMe = `Motivated Computer Science student with strong
+ analytical and problem-solving skills. Proficient in
+ programming and version control, with a high level of
+ focus and attention to detail. Eager to apply academic
+ knowledge to real-world projects and contribute to
+ innovative technology solutions.
+ In addition to my academic pursuits,
+ I enjoy traveling and staying active through weightlifting.
+ I have a keen interest in electronic music and often explore various genres.
+ I believe in maintaining a balanced lifestyle that complements my professional development.`
+
+ modernVotes = 0
+ classicVotes = 0
+ minimalVotes = 0
+ currentTheme = "classic"
+ modernLink = "https://www.google.com"
+ classicLink = "https://www.google.com"
+ minimalLink = "https://www.google.com"
+ hof.Register()
+}
+
+func UpdatePFP(url, caption string) {
+ AssertAuthorized()
+ pfp = url
+ pfpCaption = caption
+}
+
+func UpdateAboutMe(col1 string) {
+ AssertAuthorized()
+ abtMe = col1
+}
+
+func maxOfThree(a, b, c int64) int64 {
+ max := a
+ if b > max {
+ max = b
+ }
+ if c > max {
+ max = c
+ }
+ return max
+}
+
+func VoteModern() {
+ ugnotAmount := std.GetOrigSend().AmountOf("ugnot")
+ votes := ugnotAmount
+ modernVotes += votes
+ updateCurrentTheme()
+}
+
+func VoteClassic() {
+ ugnotAmount := std.GetOrigSend().AmountOf("ugnot")
+ votes := ugnotAmount
+ classicVotes += votes
+ updateCurrentTheme()
+}
+
+func VoteMinimal() {
+ ugnotAmount := std.GetOrigSend().AmountOf("ugnot")
+ votes := ugnotAmount
+ minimalVotes += votes
+ updateCurrentTheme()
+}
+
+func updateCurrentTheme() {
+ maxVotes := maxOfThree(modernVotes, classicVotes, minimalVotes)
+
+ if maxVotes == modernVotes {
+ currentTheme = "modern"
+ } else if maxVotes == classicVotes {
+ currentTheme = "classic"
+ } else {
+ currentTheme = "minimal"
+ }
+}
+
+func CollectBalance() {
+ AssertAuthorized()
+
+ banker := std.GetBanker(std.BankerTypeRealmSend)
+ ownerAddr := Address()
+
+ banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))
+}
+
+func Render(path string) string {
+ var sb strings.Builder
+
+ // Theme-specific header styling
+ switch currentTheme {
+ case "modern":
+ // Modern theme - Clean and minimalist with emojis
+ sb.WriteString(md.H1("🚀 Matija's Space"))
+ sb.WriteString(md.Image(pfpCaption, pfp))
+ sb.WriteString("\n")
+ sb.WriteString(md.Italic(pfpCaption))
+ sb.WriteString("\n")
+ sb.WriteString(md.HorizontalRule())
+ sb.WriteString(abtMe)
+ sb.WriteString("\n")
+
+ case "minimal":
+ // Minimal theme - No emojis, minimal formatting
+ sb.WriteString(md.H1("Matija Marjanovic"))
+ sb.WriteString("\n")
+ sb.WriteString(abtMe)
+ sb.WriteString("\n")
+ sb.WriteString(md.Image(pfpCaption, pfp))
+ sb.WriteString("\n")
+ sb.WriteString(pfpCaption)
+ sb.WriteString("\n")
+
+ default: // classic
+ // Classic theme - Traditional blog style with decorative elements
+ sb.WriteString(md.H1("✨ Welcome to Matija's Homepage ✨"))
+ sb.WriteString("\n")
+ sb.WriteString(md.Image(pfpCaption, pfp))
+ sb.WriteString("\n")
+ sb.WriteString(pfpCaption)
+ sb.WriteString("\n")
+ sb.WriteString(md.HorizontalRule())
+ sb.WriteString(md.H2("About me"))
+ sb.WriteString("\n")
+ sb.WriteString(abtMe)
+ sb.WriteString("\n")
+ }
+
+ // Theme-specific voting section
+ switch currentTheme {
+ case "modern":
+ sb.WriteString(md.HorizontalRule())
+ sb.WriteString(md.H2("🎨 Theme Selector"))
+ sb.WriteString("Choose your preferred viewing experience:\n")
+ items := []string{
+ md.Link(ufmt.Sprintf("Modern Design (%d votes)", modernVotes), modernLink),
+ md.Link(ufmt.Sprintf("Classic Style (%d votes)", classicVotes), classicLink),
+ md.Link(ufmt.Sprintf("Minimal Look (%d votes)", minimalVotes), minimalLink),
+ }
+ sb.WriteString(md.BulletList(items))
+
+ case "minimal":
+ sb.WriteString("\n")
+ sb.WriteString(md.H3("Theme Selection"))
+ sb.WriteString(ufmt.Sprintf("Current theme: %s\n", currentTheme))
+ sb.WriteString(ufmt.Sprintf("Votes - Modern: %d | Classic: %d | Minimal: %d\n",
+ modernVotes, classicVotes, minimalVotes))
+ sb.WriteString(md.Link("Modern", modernLink))
+ sb.WriteString(" | ")
+ sb.WriteString(md.Link("Classic", classicLink))
+ sb.WriteString(" | ")
+ sb.WriteString(md.Link("Minimal", minimalLink))
+ sb.WriteString("\n")
+
+ default: // classic
+ sb.WriteString(md.HorizontalRule())
+ sb.WriteString(md.H2("✨ Theme Customization ✨"))
+ sb.WriteString(md.Bold("Choose Your Preferred Theme:"))
+ sb.WriteString("\n\n")
+ items := []string{
+ ufmt.Sprintf("Modern 🚀 (%d votes) - %s", modernVotes, md.Link("Vote", modernLink)),
+ ufmt.Sprintf("Classic ✨ (%d votes) - %s", classicVotes, md.Link("Vote", classicLink)),
+ ufmt.Sprintf("Minimal ⚡ (%d votes) - %s", minimalVotes, md.Link("Vote", minimalLink)),
+ }
+ sb.WriteString(md.BulletList(items))
+ }
+
+ // Theme-specific footer/links section
+ switch currentTheme {
+ case "modern":
+ sb.WriteString(md.HorizontalRule())
+ sb.WriteString(md.Link("GitHub", "https://github.com/matijamarjanovic"))
+ sb.WriteString(" | ")
+ sb.WriteString(md.Link("LinkedIn", "https://www.linkedin.com/in/matijamarjanovic"))
+ sb.WriteString("\n")
+
+ case "minimal":
+ sb.WriteString("\n")
+ sb.WriteString(md.Link("GitHub", "https://github.com/matijamarjanovic"))
+ sb.WriteString(" | ")
+ sb.WriteString(md.Link("LinkedIn", "https://www.linkedin.com/in/matijamarjanovic"))
+ sb.WriteString("\n")
+
+ default: // classic
+ sb.WriteString(md.HorizontalRule())
+ sb.WriteString(md.H3("✨ Connect With Me"))
+ items := []string{
+ md.Link("🌟 GitHub", "https://github.com/matijamarjanovic"),
+ md.Link("💼 LinkedIn", "https://www.linkedin.com/in/matijamarjanovic"),
+ }
+ sb.WriteString(md.BulletList(items))
+ }
+
+ return sb.String()
+}
+
+func UpdateModernLink(link string) {
+ AssertAuthorized()
+ modernLink = link
+}
+
+func UpdateClassicLink(link string) {
+ AssertAuthorized()
+ classicLink = link
+}
+
+func UpdateMinimalLink(link string) {
+ AssertAuthorized()
+ minimalLink = link
+}
diff --git a/examples/gno.land/r/matijamarjanovic/home/home_test.gno b/examples/gno.land/r/matijamarjanovic/home/home_test.gno
new file mode 100644
index 00000000000..8cc6e6e5608
--- /dev/null
+++ b/examples/gno.land/r/matijamarjanovic/home/home_test.gno
@@ -0,0 +1,134 @@
+package home
+
+import (
+ "std"
+ "strings"
+ "testing"
+
+ "gno.land/p/demo/uassert"
+ "gno.land/p/demo/urequire"
+)
+
+// Helper function to set up test environment
+func setupTest() {
+ std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y"))
+}
+
+func TestUpdatePFP(t *testing.T) {
+ setupTest()
+ pfp = ""
+ pfpCaption = ""
+
+ UpdatePFP("https://example.com/pic.png", "New Caption")
+
+ urequire.Equal(t, pfp, "https://example.com/pic.png", "Profile picture URL should be updated")
+ urequire.Equal(t, pfpCaption, "New Caption", "Profile picture caption should be updated")
+}
+
+func TestUpdateAboutMe(t *testing.T) {
+ setupTest()
+ abtMe = ""
+
+ UpdateAboutMe("This is my new bio.")
+
+ urequire.Equal(t, abtMe, "This is my new bio.", "About Me should be updated")
+}
+
+func TestVoteModern(t *testing.T) {
+ setupTest()
+ modernVotes, classicVotes, minimalVotes = 0, 0, 0
+
+ coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000))
+ coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1))
+
+ std.TestSetOrigSend(coinsSent, coinsSpent)
+ VoteModern()
+
+ uassert.Equal(t, int64(75000000), modernVotes, "Modern votes should be calculated correctly")
+ uassert.Equal(t, "modern", currentTheme, "Theme should be updated to modern")
+}
+
+func TestVoteClassic(t *testing.T) {
+ setupTest()
+ modernVotes, classicVotes, minimalVotes = 0, 0, 0
+
+ coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000))
+ coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1))
+
+ std.TestSetOrigSend(coinsSent, coinsSpent)
+ VoteClassic()
+
+ uassert.Equal(t, int64(75000000), classicVotes, "Classic votes should be calculated correctly")
+ uassert.Equal(t, "classic", currentTheme, "Theme should be updated to classic")
+}
+
+func TestVoteMinimal(t *testing.T) {
+ setupTest()
+ modernVotes, classicVotes, minimalVotes = 0, 0, 0
+
+ coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000))
+ coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1))
+
+ std.TestSetOrigSend(coinsSent, coinsSpent)
+ VoteMinimal()
+
+ uassert.Equal(t, int64(75000000), minimalVotes, "Minimal votes should be calculated correctly")
+ uassert.Equal(t, "minimal", currentTheme, "Theme should be updated to minimal")
+}
+
+func TestRender(t *testing.T) {
+ setupTest()
+ // Reset the state to known values
+ modernVotes, classicVotes, minimalVotes = 0, 0, 0
+ currentTheme = "classic"
+ pfp = "https://example.com/pic.png"
+ pfpCaption = "Test Caption"
+ abtMe = "Test About Me"
+
+ out := Render("")
+ urequire.NotEqual(t, out, "", "Render output should not be empty")
+
+ // Test classic theme specific content
+ uassert.True(t, strings.Contains(out, "✨ Welcome to Matija's Homepage ✨"), "Classic theme should have correct header")
+ uassert.True(t, strings.Contains(out, pfp), "Should contain profile picture URL")
+ uassert.True(t, strings.Contains(out, pfpCaption), "Should contain profile picture caption")
+ uassert.True(t, strings.Contains(out, "About me"), "Should contain About me section")
+ uassert.True(t, strings.Contains(out, abtMe), "Should contain about me content")
+ uassert.True(t, strings.Contains(out, "Theme Customization"), "Should contain theme customization section")
+ uassert.True(t, strings.Contains(out, "Connect With Me"), "Should contain connect section")
+}
+
+func TestRenderModernTheme(t *testing.T) {
+ setupTest()
+ modernVotes, classicVotes, minimalVotes = 100, 0, 0
+ currentTheme = "modern"
+ updateCurrentTheme()
+
+ out := Render("")
+ uassert.True(t, strings.Contains(out, "🚀 Matija's Space"), "Modern theme should have correct header")
+}
+
+func TestRenderMinimalTheme(t *testing.T) {
+ setupTest()
+ modernVotes, classicVotes, minimalVotes = 0, 0, 100
+ currentTheme = "minimal"
+ updateCurrentTheme()
+
+ out := Render("")
+ uassert.True(t, strings.Contains(out, "Matija Marjanovic"), "Minimal theme should have correct header")
+}
+
+func TestUpdateLinks(t *testing.T) {
+ setupTest()
+
+ newLink := "https://example.com/vote"
+
+ UpdateModernLink(newLink)
+ urequire.Equal(t, modernLink, newLink, "Modern link should be updated")
+
+ UpdateClassicLink(newLink)
+ urequire.Equal(t, classicLink, newLink, "Classic link should be updated")
+
+ UpdateMinimalLink(newLink)
+ urequire.Equal(t, minimalLink, newLink, "Minimal link should be updated")
+}
diff --git a/examples/gno.land/r/moul/config/config_test.gno b/examples/gno.land/r/moul/config/config_test.gno
new file mode 100644
index 00000000000..d912156bec0
--- /dev/null
+++ b/examples/gno.land/r/moul/config/config_test.gno
@@ -0,0 +1 @@
+package config
diff --git a/examples/gno.land/r/moul/home/home.gno b/examples/gno.land/r/moul/home/home.gno
index 140e7b5e0c8..1094ce29cc5 100644
--- a/examples/gno.land/r/moul/home/home.gno
+++ b/examples/gno.land/r/moul/home/home.gno
@@ -1,14 +1,23 @@
package home
import (
+ "strconv"
+
+ "gno.land/p/demo/svg"
+ "gno.land/p/moul/debug"
+ "gno.land/p/moul/md"
+ "gno.land/p/moul/mdtable"
+ "gno.land/p/moul/txlink"
+ "gno.land/p/moul/web25"
"gno.land/r/leon/hof"
"gno.land/r/moul/config"
)
var (
- todos []string
- status string
- memeImgURL string
+ todos []string
+ status string
+ memeImgURL string
+ web25config = web25.Config{URL: "https://moul.github.io/gno-moul-home-web25/"}
)
func init() {
@@ -19,36 +28,74 @@ func init() {
}
func Render(path string) string {
- content := "# Manfred's (gn)home Dashboard\n\n"
+ content := web25config.Render(path)
+ var d debug.Debug
+
+ content += md.H1("Manfred's (gn)home Dashboard")
- content += "## Meme\n"
- content += "![](" + memeImgURL + ")\n\n"
+ content += md.H2("Meme")
+ content += md.Paragraph(
+ md.Image("meme", memeImgURL),
+ )
- content += "## Status\n"
- content += status + "\n\n"
+ content += md.H2("Status")
+ content += md.Paragraph(status)
+ content += md.Paragraph(md.Link("update", txlink.Call("UpdateStatus")))
- content += "## Personal ToDo List\n"
- for _, todo := range todos {
- content += "- [ ] " + todo + "\n"
+ d.Log("hello world!")
+
+ content += md.H2("Personal TODO List (bullet list)")
+ for i, todo := range todos {
+ idstr := strconv.Itoa(i)
+ deleteLink := md.Link("x", txlink.Call("DeleteTodo", "idx", idstr))
+ content += md.BulletItem(todo + " " + deleteLink)
}
- content += "\n"
+ content += md.BulletItem(md.Link("[new]", txlink.Call("AddTodo")))
+
+ content += md.H2("Personal TODO List (table)")
+ table := mdtable.Table{
+ Headers: []string{"ID", "Item", "Links"},
+ }
+ for i, todo := range todos {
+ idstr := strconv.Itoa(i)
+ deleteLink := md.Link("[del]", txlink.Call("DeleteTodo", "idx", idstr))
+ table.Append([]string{"#" + idstr, todo, deleteLink})
+ }
+ content += table.String()
+
+ content += md.H2("SVG Example")
+ content += md.Paragraph("this feature may not work with the current gnoweb version and/or configuration.")
+ content += md.Paragraph(svg.Canvas{
+ Width: 500, Height: 500,
+ Elems: []svg.Elem{
+ svg.Rectangle{50, 50, 100, 100, "red"},
+ svg.Circle{50, 50, 100, "red"},
+ svg.Text{100, 100, "hello world!", "magenta"},
+ },
+ }.String())
- // TODO: Implement a feature to list replies on r/boards on my posts
- // TODO: Maybe integrate a calendar feature for upcoming events?
+ content += md.H2("Debug")
+ content += md.Paragraph("this feature may not work with the current gnoweb version and/or configuration.")
+ content += md.Paragraph(
+ md.Link("toggle debug", debug.ToggleURL(path)),
+ )
+ // TODO: my r/boards posts
+ // TODO: my r/events events
+ content += d.Render(path)
return content
}
-func AddNewTodo(todo string) {
+func AddTodo(todo string) {
config.AssertIsAdmin()
todos = append(todos, todo)
}
-func DeleteTodo(todoIndex int) {
+func DeleteTodo(idx int) {
config.AssertIsAdmin()
- if todoIndex >= 0 && todoIndex < len(todos) {
+ if idx >= 0 && idx < len(todos) {
// Remove the todo from the list by merging slices from before and after the todo
- todos = append(todos[:todoIndex], todos[todoIndex+1:]...)
+ todos = append(todos[:idx], todos[idx+1:]...)
} else {
panic("Invalid todo index")
}
diff --git a/examples/gno.land/r/moul/home/z1_filetest.gno b/examples/gno.land/r/moul/home/z1_filetest.gno
index b26c919dd3a..b9d7d91a702 100644
--- a/examples/gno.land/r/moul/home/z1_filetest.gno
+++ b/examples/gno.land/r/moul/home/z1_filetest.gno
@@ -7,15 +7,31 @@ func main() {
}
// Output:
+// Click [here](https://moul.github.io/gno-moul-home-web25/) to visit the full rendering experience.
// # Manfred's (gn)home Dashboard
-//
// ## Meme
-// ![](https://i.imgflip.com/7ze8dc.jpg)
+// ![meme](https://i.imgflip.com/7ze8dc.jpg)
//
// ## Status
// Online
//
-// ## Personal ToDo List
-// - [ ] fill this todo list...
+// [update](/r/moul/home$help&func=UpdateStatus)
+//
+// ## Personal TODO List (bullet list)
+// - fill this todo list... [x](/r/moul/home$help&func=DeleteTodo&idx=0)
+// - [\[new\]](/r/moul/home$help&func=AddTodo)
+// ## Personal TODO List (table)
+// | ID | Item | Links |
+// | --- | --- | --- |
+// | #0 | fill this todo list... | [\[del\]](/r/moul/home$help&func=DeleteTodo&idx=0) |
+// ## SVG Example
+// this feature may not work with the current gnoweb version and/or configuration.
+//
+//
+//
+// ## Debug
+// this feature may not work with the current gnoweb version and/or configuration.
+//
+// [toggle debug](/r/moul/home:?debug=1)
//
//
diff --git a/examples/gno.land/r/moul/home/z2_filetest.gno b/examples/gno.land/r/moul/home/z2_filetest.gno
index 489dc2aeecd..f471280d8ef 100644
--- a/examples/gno.land/r/moul/home/z2_filetest.gno
+++ b/examples/gno.land/r/moul/home/z2_filetest.gno
@@ -8,30 +8,65 @@ import (
func main() {
std.TestSetOrigCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5")
- home.AddNewTodo("aaa")
- home.AddNewTodo("bbb")
- home.AddNewTodo("ccc")
- home.AddNewTodo("ddd")
- home.AddNewTodo("eee")
+ home.AddTodo("aaa")
+ home.AddTodo("bbb")
+ home.AddTodo("ccc")
+ home.AddTodo("ddd")
+ home.AddTodo("eee")
home.UpdateStatus("Lorem Ipsum")
home.DeleteTodo(3)
- println(home.Render(""))
+ println(home.Render("?debug=1"))
}
// Output:
+// Click [here](https://moul.github.io/gno-moul-home-web25/) to visit the full rendering experience.
// # Manfred's (gn)home Dashboard
-//
// ## Meme
-// ![](https://i.imgflip.com/7ze8dc.jpg)
+// ![meme](https://i.imgflip.com/7ze8dc.jpg)
//
// ## Status
// Lorem Ipsum
//
-// ## Personal ToDo List
-// - [ ] fill this todo list...
-// - [ ] aaa
-// - [ ] bbb
-// - [ ] ddd
-// - [ ] eee
+// [update](/r/moul/home$help&func=UpdateStatus)
+//
+// ## Personal TODO List (bullet list)
+// - fill this todo list... [x](/r/moul/home$help&func=DeleteTodo&idx=0)
+// - aaa [x](/r/moul/home$help&func=DeleteTodo&idx=1)
+// - bbb [x](/r/moul/home$help&func=DeleteTodo&idx=2)
+// - ddd [x](/r/moul/home$help&func=DeleteTodo&idx=3)
+// - eee [x](/r/moul/home$help&func=DeleteTodo&idx=4)
+// - [\[new\]](/r/moul/home$help&func=AddTodo)
+// ## Personal TODO List (table)
+// | ID | Item | Links |
+// | --- | --- | --- |
+// | #0 | fill this todo list... | [\[del\]](/r/moul/home$help&func=DeleteTodo&idx=0) |
+// | #1 | aaa | [\[del\]](/r/moul/home$help&func=DeleteTodo&idx=1) |
+// | #2 | bbb | [\[del\]](/r/moul/home$help&func=DeleteTodo&idx=2) |
+// | #3 | ddd | [\[del\]](/r/moul/home$help&func=DeleteTodo&idx=3) |
+// | #4 | eee | [\[del\]](/r/moul/home$help&func=DeleteTodo&idx=4) |
+// ## SVG Example
+// this feature may not work with the current gnoweb version and/or configuration.
+//
+//
+//
+// ## Debug
+// this feature may not work with the current gnoweb version and/or configuration.
+//
+// [toggle debug](/r/moul/home:)
+//
+// debug
+//
+// ### Logs
+// - hello world!
+// ### Metadata
+// | Key | Value |
+// | --- | --- |
+// | `std.CurrentRealm().PkgPath()` | gno.land/r/moul/home |
+// | `std.CurrentRealm().Addr()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z |
+// | `std.PrevRealm().PkgPath()` | |
+// | `std.PrevRealm().Addr()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 |
+// | `std.GetHeight()` | 123 |
+// | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z |
//
+//
//
diff --git a/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar b/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar
new file mode 100644
index 00000000000..56050f4733b
--- /dev/null
+++ b/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar
@@ -0,0 +1,57 @@
+# ensure users get proper out of gas errors when they add packages
+
+# start a new node
+gnoland start
+
+# add foo package
+gnokey maketx addpkg -pkgdir $WORK/foo -pkgpath gno.land/r/foo -gas-fee 1000000ugnot -gas-wanted 220000 -broadcast -chainid=tendermint_test test1
+
+
+# add bar package
+# out of gas at store.GetPackage() with gas 60000
+
+! gnokey maketx addpkg -pkgdir $WORK/bar -pkgpath gno.land/r/bar -gas-fee 1000000ugnot -gas-wanted 60000 -broadcast -chainid=tendermint_test test1
+
+# Out of gas error
+
+stderr '--= Error =--'
+stderr 'Data: out of gas error'
+stderr 'Msg Traces:'
+stderr 'out of gas.*?in preprocess'
+stderr '--= /Error =--'
+
+
+
+# out of gas at store.store.GetTypeSafe() with gas 63000
+
+! gnokey maketx addpkg -pkgdir $WORK/bar -pkgpath gno.land/r/bar -gas-fee 1000000ugnot -gas-wanted 63000 -broadcast -chainid=tendermint_test test1
+
+stderr '--= Error =--'
+stderr 'Data: out of gas error'
+stderr 'Msg Traces:'
+stderr 'out of gas.*?in preprocess'
+stderr '--= /Error =--'
+
+
+-- foo/foo.gno --
+package foo
+
+type Counter int
+
+func Inc(i Counter) Counter{
+ i = i+1
+ return i
+}
+
+-- bar/bar.gno --
+package bar
+
+import "gno.land/r/foo"
+
+type NewCounter foo.Counter
+
+func Add2(i NewCounter) NewCounter{
+ i=i+2
+
+ return i
+}
diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go
index 6d5399ca932..a3e7f5310e1 100644
--- a/gnovm/cmd/gno/lint.go
+++ b/gnovm/cmd/gno/lint.go
@@ -6,17 +6,19 @@ import (
"flag"
"fmt"
"go/scanner"
+ "go/types"
"io"
"os"
"path/filepath"
"regexp"
"strings"
+ "github.com/gnolang/gno/gnovm"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
+ "github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/gnovm/pkg/test"
"github.com/gnolang/gno/tm2/pkg/commands"
- osm "github.com/gnolang/gno/tm2/pkg/os"
"go.uber.org/multierr"
)
@@ -50,6 +52,31 @@ func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)")
}
+type lintCode int
+
+const (
+ lintUnknown lintCode = iota
+ lintGnoMod
+ lintGnoError
+ lintParserError
+ lintTypeCheckError
+
+ // TODO: add new linter codes here.
+)
+
+type lintIssue struct {
+ Code lintCode
+ Msg string
+ Confidence float64 // 1 is 100%
+ Location string // file:line, or equivalent
+ // TODO: consider writing fix suggestions
+}
+
+func (i lintIssue) String() string {
+ // TODO: consider crafting a doc URL based on Code.
+ return fmt.Sprintf("%s: %s (code=%d)", i.Location, i.Msg, i.Code)
+}
+
func execLint(cfg *lintCfg, args []string, io commands.IO) error {
if len(args) < 1 {
return flag.ErrHelp
@@ -72,37 +99,55 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
for _, pkgPath := range pkgPaths {
if verbose {
- fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath)
+ io.ErrPrintln(pkgPath)
+ }
+
+ info, err := os.Stat(pkgPath)
+ if err == nil && !info.IsDir() {
+ pkgPath = filepath.Dir(pkgPath)
}
// Check if 'gno.mod' exists
- gnoModPath := filepath.Join(pkgPath, "gno.mod")
- if !osm.FileExists(gnoModPath) {
- hasError = true
+ gmFile, err := gnomod.ParseAt(pkgPath)
+ if err != nil {
issue := lintIssue{
- Code: lintNoGnoMod,
+ Code: lintGnoMod,
Confidence: 1,
Location: pkgPath,
- Msg: "missing 'gno.mod' file",
+ Msg: err.Error(),
}
- fmt.Fprint(io.Err(), issue.String()+"\n")
+ io.ErrPrintln(issue)
+ hasError = true
}
- // Handle runtime errors
- hasError = catchRuntimeError(pkgPath, io.Err(), func() {
- stdout, stdin, stderr := io.Out(), io.In(), io.Err()
- _, testStore := test.Store(
- rootDir, false,
- stdin, stdout, stderr,
- )
-
- targetPath := pkgPath
- info, err := os.Stat(pkgPath)
- if err == nil && !info.IsDir() {
- targetPath = filepath.Dir(pkgPath)
+ stdout, stdin, stderr := io.Out(), io.In(), io.Err()
+ _, testStore := test.Store(
+ rootDir, false,
+ stdin, stdout, stderr,
+ )
+
+ memPkg, err := gno.ReadMemPackage(pkgPath, pkgPath)
+ if err != nil {
+ io.ErrPrintln(issueFromError(pkgPath, err).String())
+ hasError = true
+ continue
+ }
+
+ // Run type checking
+ if gmFile == nil || !gmFile.Draft {
+ foundErr, err := lintTypeCheck(io, memPkg, testStore)
+ if err != nil {
+ io.ErrPrintln(err)
+ hasError = true
+ } else if foundErr {
+ hasError = true
}
+ } else if verbose {
+ io.ErrPrintfln("%s: module is draft, skipping type check", pkgPath)
+ }
- memPkg := gno.MustReadMemPackage(targetPath, targetPath)
+ // Handle runtime errors
+ hasRuntimeErr := catchRuntimeError(pkgPath, io.Err(), func() {
tm := test.Machine(testStore, stdout, memPkg.Path)
defer tm.Release()
@@ -110,28 +155,13 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
tm.RunMemPackage(memPkg, true)
// Check test files
- testfiles := &gno.FileSet{}
- for _, mfile := range memPkg.Files {
- if !strings.HasSuffix(mfile.Name, ".gno") {
- continue // Skip non-GNO files
- }
+ testFiles := lintTestFiles(memPkg)
- n, _ := gno.ParseFile(mfile.Name, mfile.Body)
- if n == nil {
- continue // Skip empty files
- }
-
- // XXX: package ending with `_test` is not supported yet
- if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") {
- // Keep only test files
- testfiles.AddFiles(n)
- }
- }
-
- tm.RunFiles(testfiles.Files...)
- }) || hasError
-
- // TODO: Add more checkers
+ tm.RunFiles(testFiles.Files...)
+ })
+ if hasRuntimeErr {
+ hasError = true
+ }
}
if hasError {
@@ -141,6 +171,66 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
return nil
}
+func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, testStore gno.Store) (errorsFound bool, err error) {
+ tcErr := gno.TypeCheckMemPackageTest(memPkg, testStore)
+ if tcErr == nil {
+ return false, nil
+ }
+
+ errs := multierr.Errors(tcErr)
+ for _, err := range errs {
+ switch err := err.(type) {
+ case types.Error:
+ io.ErrPrintln(lintIssue{
+ Code: lintTypeCheckError,
+ Msg: err.Msg,
+ Confidence: 1,
+ Location: err.Fset.Position(err.Pos).String(),
+ })
+ case scanner.ErrorList:
+ for _, scErr := range err {
+ io.ErrPrintln(lintIssue{
+ Code: lintParserError,
+ Msg: scErr.Msg,
+ Confidence: 1,
+ Location: scErr.Pos.String(),
+ })
+ }
+ case scanner.Error:
+ io.ErrPrintln(lintIssue{
+ Code: lintParserError,
+ Msg: err.Msg,
+ Confidence: 1,
+ Location: err.Pos.String(),
+ })
+ default:
+ return false, fmt.Errorf("unexpected error type: %T", err)
+ }
+ }
+ return true, nil
+}
+
+func lintTestFiles(memPkg *gnovm.MemPackage) *gno.FileSet {
+ testfiles := &gno.FileSet{}
+ for _, mfile := range memPkg.Files {
+ if !strings.HasSuffix(mfile.Name, ".gno") {
+ continue // Skip non-GNO files
+ }
+
+ n, _ := gno.ParseFile(mfile.Name, mfile.Body)
+ if n == nil {
+ continue // Skip empty files
+ }
+
+ // XXX: package ending with `_test` is not supported yet
+ if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") {
+ // Keep only test files
+ testfiles.AddFiles(n)
+ }
+ }
+ return testfiles
+}
+
func guessSourcePath(pkg, source string) string {
if info, err := os.Stat(pkg); !os.IsNotExist(err) && !info.IsDir() {
pkg = filepath.Dir(pkg)
@@ -174,21 +264,21 @@ func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (ha
switch verr := r.(type) {
case *gno.PreprocessError:
err := verr.Unwrap()
- fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n")
+ fmt.Fprintln(stderr, issueFromError(pkgPath, err).String())
case error:
errors := multierr.Errors(verr)
for _, err := range errors {
errList, ok := err.(scanner.ErrorList)
if ok {
for _, errorInList := range errList {
- fmt.Fprint(stderr, issueFromError(pkgPath, errorInList).String()+"\n")
+ fmt.Fprintln(stderr, issueFromError(pkgPath, errorInList).String())
}
} else {
- fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n")
+ fmt.Fprintln(stderr, issueFromError(pkgPath, err).String())
}
}
case string:
- fmt.Fprint(stderr, issueFromError(pkgPath, errors.New(verr)).String()+"\n")
+ fmt.Fprintln(stderr, issueFromError(pkgPath, errors.New(verr)).String())
default:
panic(r)
}
@@ -198,29 +288,6 @@ func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (ha
return
}
-type lintCode int
-
-const (
- lintUnknown lintCode = 0
- lintNoGnoMod lintCode = iota
- lintGnoError
-
- // TODO: add new linter codes here.
-)
-
-type lintIssue struct {
- Code lintCode
- Msg string
- Confidence float64 // 1 is 100%
- Location string // file:line, or equivalent
- // TODO: consider writing fix suggestions
-}
-
-func (i lintIssue) String() string {
- // TODO: consider crafting a doc URL based on Code.
- return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code)
-}
-
func issueFromError(pkgPath string, err error) lintIssue {
var issue lintIssue
issue.Confidence = 1
diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go
index 031c252bc79..4589fc55f92 100644
--- a/gnovm/cmd/gno/lint_test.go
+++ b/gnovm/cmd/gno/lint_test.go
@@ -1,6 +1,9 @@
package main
-import "testing"
+import (
+ "strings"
+ "testing"
+)
func TestLintApp(t *testing.T) {
tc := []testMainCase{
@@ -9,7 +12,7 @@ func TestLintApp(t *testing.T) {
errShouldBe: "flag: help requested",
}, {
args: []string{"lint", "../../tests/integ/run_main/"},
- stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",
+ stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"},
@@ -17,33 +20,43 @@ func TestLintApp(t *testing.T) {
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"},
- stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2).",
+ stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"},
- stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-lint-errors/main.gno:6",
+ stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6",
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"},
- stderrShouldContain: "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2).\n../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n",
- errShouldBe: "exit code: 1",
- }, {
- args: []string{"lint", "../../tests/integ/run_main/"},
- stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",
- errShouldBe: "exit code: 1",
+ args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"},
+ stderrShouldContain: func() string {
+ lines := []string{
+ "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)",
+ "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)",
+ "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)",
+ "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)",
+ }
+ return strings.Join(lines, "\n") + "\n"
+ }(),
+ errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/minimalist_gnomod/"},
// TODO: raise an error because there is a gno.mod, but no .gno files
}, {
args: []string{"lint", "../../tests/integ/invalid_module_name/"},
// TODO: raise an error because gno.mod is invalid
+ }, {
+ args: []string{"lint", "../../tests/integ/invalid_gno_file/"},
+ stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)",
+ errShouldBe: "exit code: 1",
+ }, {
+ args: []string{"lint", "../../tests/integ/typecheck_missing_return/"},
+ stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)",
+ errShouldBe: "exit code: 1",
},
// TODO: 'gno mod' is valid?
- // TODO: is gno source valid?
// TODO: are dependencies valid?
// TODO: is gno source using unsafe/discouraged features?
- // TODO: consider making `gno transpile; go lint *gen.go`
// TODO: check for imports of native libs from non _test.gno files
}
testMainCaseRun(t, tc)
diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go
index 74f99f7490c..aa7780c149e 100644
--- a/gnovm/cmd/gno/run_test.go
+++ b/gnovm/cmd/gno/run_test.go
@@ -1,6 +1,9 @@
package main
-import "testing"
+import (
+ "strings"
+ "testing"
+)
func TestRunApp(t *testing.T) {
tc := []testMainCase{
@@ -84,9 +87,17 @@ func TestRunApp(t *testing.T) {
stdoutShouldContain: "Context worked",
},
{
- args: []string{"run", "../../tests/integ/several-files-multiple-errors/"},
- stderrShouldContain: "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2).\n../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n",
- errShouldBe: "exit code: 1",
+ args: []string{"run", "../../tests/integ/several-files-multiple-errors/"},
+ stderrShouldContain: func() string {
+ lines := []string{
+ "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)",
+ "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)",
+ "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)",
+ "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)",
+ }
+ return strings.Join(lines, "\n") + "\n"
+ }(),
+ errShouldBe: "exit code: 1",
},
// TODO: a test file
// TODO: args
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar b/gnovm/cmd/gno/testdata/fmt/empty.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/empty.txtar
rename to gnovm/cmd/gno/testdata/fmt/empty.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar b/gnovm/cmd/gno/testdata/fmt/import_cleaning.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar
rename to gnovm/cmd/gno/testdata/fmt/import_cleaning.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/include.txtar b/gnovm/cmd/gno/testdata/fmt/include.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/include.txtar
rename to gnovm/cmd/gno/testdata/fmt/include.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar b/gnovm/cmd/gno/testdata/fmt/multi_import.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar
rename to gnovm/cmd/gno/testdata/fmt/multi_import.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar b/gnovm/cmd/gno/testdata/fmt/noimport_format.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar
rename to gnovm/cmd/gno/testdata/fmt/noimport_format.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar b/gnovm/cmd/gno/testdata/fmt/parse_error.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar
rename to gnovm/cmd/gno/testdata/fmt/parse_error.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar b/gnovm/cmd/gno/testdata/fmt/shadow_import.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar
rename to gnovm/cmd/gno/testdata/fmt/shadow_import.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar b/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar
deleted file mode 100644
index 9482eeb1f4f..00000000000
--- a/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar
+++ /dev/null
@@ -1,20 +0,0 @@
-# gno lint: test file error
-
-! gno lint ./i_have_error_test.gno
-
-cmp stdout stdout.golden
-cmp stderr stderr.golden
-
--- i_have_error_test.gno --
-package main
-
-import "fmt"
-
-func TestIHaveSomeError() {
- i := undefined_variable
- fmt.Println("Hello", 42)
-}
-
--- stdout.golden --
--- stderr.golden --
-i_have_error_test.gno:6: name undefined_variable not declared (code=2).
diff --git a/gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar
similarity index 54%
rename from gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar
rename to gnovm/cmd/gno/testdata/lint/bad_import.txtar
index 52141dff09b..b5edbdd0223 100644
--- a/gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar
+++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar
@@ -11,9 +11,13 @@ package main
import "python"
func main() {
- fmt.Println("Hello", 42)
+ println("Hello", 42)
}
+-- gno.mod --
+module gno.land/p/test
+
-- stdout.golden --
-- stderr.golden --
-bad_file.gno:3:8: unknown import path python (code=2).
+bad_file.gno:3:8: could not import python (import not found: python) (code=4)
+bad_file.gno:3:8: unknown import path python (code=2)
diff --git a/gnovm/cmd/gno/testdata/gno_lint/file_error.txtar b/gnovm/cmd/gno/testdata/lint/file_error.txtar
similarity index 88%
rename from gnovm/cmd/gno/testdata/gno_lint/file_error.txtar
rename to gnovm/cmd/gno/testdata/lint/file_error.txtar
index 5aa3a3282d5..4fa50c6da81 100644
--- a/gnovm/cmd/gno/testdata/gno_lint/file_error.txtar
+++ b/gnovm/cmd/gno/testdata/lint/file_error.txtar
@@ -15,6 +15,9 @@ func TestIHaveSomeError() {
fmt.Println("Hello", 42)
}
+-- gno.mod --
+module gno.land/p/test
+
-- stdout.golden --
-- stderr.golden --
-i_have_error_test.gno:6:7: name undefined_variable not declared (code=2).
+i_have_error_test.gno:6:7: name undefined_variable not declared (code=2)
diff --git a/gnovm/cmd/gno/testdata/gno_lint/no_error.txtar b/gnovm/cmd/gno/testdata/lint/no_error.txtar
similarity index 68%
rename from gnovm/cmd/gno/testdata/gno_lint/no_error.txtar
rename to gnovm/cmd/gno/testdata/lint/no_error.txtar
index 95356b1ba2b..5dd3b164952 100644
--- a/gnovm/cmd/gno/testdata/gno_lint/no_error.txtar
+++ b/gnovm/cmd/gno/testdata/lint/no_error.txtar
@@ -1,6 +1,6 @@
# testing simple gno lint command with any error
-gno lint ./good_file.gno
+gno lint ./good_file.gno
cmp stdout stdout.golden
cmp stdout stderr.golden
@@ -8,11 +8,12 @@ cmp stdout stderr.golden
-- good_file.gno --
package main
-import "fmt"
-
func main() {
- fmt.Println("Hello", 42)
+ println("Hello", 42)
}
+-- gno.mod --
+module gno.land/p/demo/test
+
-- stdout.golden --
-- stderr.golden --
diff --git a/gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar
similarity index 60%
rename from gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar
rename to gnovm/cmd/gno/testdata/lint/no_gnomod.txtar
index 52daa6f0e9b..b5a046a7095 100644
--- a/gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar
+++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar
@@ -8,12 +8,10 @@ cmp stderr stderr.golden
-- good_file.gno --
package main
-import "fmt"
-
func main() {
- fmt.Println("Hello", 42)
+ println("Hello", 42)
}
-- stdout.golden --
-- stderr.golden --
-./.: missing 'gno.mod' file (code=1).
+./.: parsing gno.mod at ./.: gno.mod file not found in current or any parent directory (code=1)
diff --git a/gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar
similarity index 55%
rename from gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar
rename to gnovm/cmd/gno/testdata/lint/not_declared.txtar
index b63c5c447e1..ac56b27e0df 100644
--- a/gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar
+++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar
@@ -8,13 +8,15 @@ cmp stderr stderr.golden
-- bad_file.gno --
package main
-import "fmt"
-
func main() {
- hello.Foo()
- fmt.Println("Hello", 42)
+ hello.Foo()
+ println("Hello", 42)
}
+-- gno.mod --
+module gno.land/p/demo/hello
+
-- stdout.golden --
-- stderr.golden --
-bad_file.gno:6:3: name hello not declared (code=2).
+bad_file.gno:4:2: undefined: hello (code=4)
+bad_file.gno:4:2: name hello not declared (code=2)
diff --git a/gnovm/cmd/gno/testdata/gno_test/dir_not_exist.txtar b/gnovm/cmd/gno/testdata/test/dir_not_exist.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/dir_not_exist.txtar
rename to gnovm/cmd/gno/testdata/test/dir_not_exist.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar b/gnovm/cmd/gno/testdata/test/empty_dir.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar
rename to gnovm/cmd/gno/testdata/test/empty_dir.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/test/empty_gno1.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar
rename to gnovm/cmd/gno/testdata/test/empty_gno1.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar b/gnovm/cmd/gno/testdata/test/empty_gno2.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar
rename to gnovm/cmd/gno/testdata/test/empty_gno2.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar b/gnovm/cmd/gno/testdata/test/empty_gno3.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar
rename to gnovm/cmd/gno/testdata/test/empty_gno3.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar b/gnovm/cmd/gno/testdata/test/error_correct.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/error_correct.txtar
rename to gnovm/cmd/gno/testdata/test/error_correct.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/error_incorrect.txtar b/gnovm/cmd/gno/testdata/test/error_incorrect.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/error_incorrect.txtar
rename to gnovm/cmd/gno/testdata/test/error_incorrect.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/error_sync.txtar b/gnovm/cmd/gno/testdata/test/error_sync.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/error_sync.txtar
rename to gnovm/cmd/gno/testdata/test/error_sync.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/test/failing_filetest.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar
rename to gnovm/cmd/gno/testdata/test/failing_filetest.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar b/gnovm/cmd/gno/testdata/test/failing_test.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/failing_test.txtar
rename to gnovm/cmd/gno/testdata/test/failing_test.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar b/gnovm/cmd/gno/testdata/test/filetest_events.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar
rename to gnovm/cmd/gno/testdata/test/filetest_events.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/flag_print-runtime-metrics.txtar b/gnovm/cmd/gno/testdata/test/flag_print-runtime-metrics.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/flag_print-runtime-metrics.txtar
rename to gnovm/cmd/gno/testdata/test/flag_print-runtime-metrics.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/flag_run.txtar b/gnovm/cmd/gno/testdata/test/flag_run.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/flag_run.txtar
rename to gnovm/cmd/gno/testdata/test/flag_run.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/flag_timeout.txtar b/gnovm/cmd/gno/testdata/test/flag_timeout.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/flag_timeout.txtar
rename to gnovm/cmd/gno/testdata/test/flag_timeout.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar b/gnovm/cmd/gno/testdata/test/fmt_write_import.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar
rename to gnovm/cmd/gno/testdata/test/fmt_write_import.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/minim1.txtar b/gnovm/cmd/gno/testdata/test/minim1.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/minim1.txtar
rename to gnovm/cmd/gno/testdata/test/minim1.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/minim2.txtar b/gnovm/cmd/gno/testdata/test/minim2.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/minim2.txtar
rename to gnovm/cmd/gno/testdata/test/minim2.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/minim3.txtar b/gnovm/cmd/gno/testdata/test/minim3.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/minim3.txtar
rename to gnovm/cmd/gno/testdata/test/minim3.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/multitest_events.txtar b/gnovm/cmd/gno/testdata/test/multitest_events.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/multitest_events.txtar
rename to gnovm/cmd/gno/testdata/test/multitest_events.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/no_args.txtar b/gnovm/cmd/gno/testdata/test/no_args.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/no_args.txtar
rename to gnovm/cmd/gno/testdata/test/no_args.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar b/gnovm/cmd/gno/testdata/test/output_correct.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/output_correct.txtar
rename to gnovm/cmd/gno/testdata/test/output_correct.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/output_incorrect.txtar b/gnovm/cmd/gno/testdata/test/output_incorrect.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/output_incorrect.txtar
rename to gnovm/cmd/gno/testdata/test/output_incorrect.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar b/gnovm/cmd/gno/testdata/test/output_sync.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/output_sync.txtar
rename to gnovm/cmd/gno/testdata/test/output_sync.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/panic.txtar b/gnovm/cmd/gno/testdata/test/panic.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/panic.txtar
rename to gnovm/cmd/gno/testdata/test/panic.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/pkg_underscore_test.txtar b/gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/pkg_underscore_test.txtar
rename to gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar
rename to gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar
rename to gnovm/cmd/gno/testdata/test/realm_correct.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar
rename to gnovm/cmd/gno/testdata/test/realm_incorrect.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar
rename to gnovm/cmd/gno/testdata/test/realm_sync.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/recover.txtar b/gnovm/cmd/gno/testdata/test/recover.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/recover.txtar
rename to gnovm/cmd/gno/testdata/test/recover.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/skip.txtar b/gnovm/cmd/gno/testdata/test/skip.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/skip.txtar
rename to gnovm/cmd/gno/testdata/test/skip.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/unknown_package.txtar b/gnovm/cmd/gno/testdata/test/unknown_package.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/unknown_package.txtar
rename to gnovm/cmd/gno/testdata/test/unknown_package.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar b/gnovm/cmd/gno/testdata/test/valid_filetest.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar
rename to gnovm/cmd/gno/testdata/test/valid_filetest.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar b/gnovm/cmd/gno/testdata/test/valid_test.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_test/valid_test.txtar
rename to gnovm/cmd/gno/testdata/test/valid_test.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar
rename to gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar
rename to gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar
rename to gnovm/cmd/gno/testdata/transpile/invalid_import.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar b/gnovm/cmd/gno/testdata/transpile/no_args.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar
rename to gnovm/cmd/gno/testdata/transpile/no_args.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar
rename to gnovm/cmd/gno/testdata/transpile/parse_error.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar
diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar
similarity index 100%
rename from gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar
rename to gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar
diff --git a/gnovm/cmd/gno/testdata_test.go b/gnovm/cmd/gno/testdata_test.go
index 15bc8d96e26..6b1bbd1d459 100644
--- a/gnovm/cmd/gno/testdata_test.go
+++ b/gnovm/cmd/gno/testdata_test.go
@@ -24,7 +24,6 @@ func Test_Scripts(t *testing.T) {
}
name := dir.Name()
- t.Logf("testing: %s", name)
t.Run(name, func(t *testing.T) {
updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS"))
p := testscript.Params{
diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go
index 827c09e23f1..5a03ddc7657 100644
--- a/gnovm/cmd/gno/transpile_test.go
+++ b/gnovm/cmd/gno/transpile_test.go
@@ -6,29 +6,9 @@ import (
"strconv"
"testing"
- "github.com/rogpeppe/go-internal/testscript"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/gnolang/gno/gnovm/pkg/integration"
)
-func Test_ScriptsTranspile(t *testing.T) {
- p := testscript.Params{
- Dir: "testdata/gno_transpile",
- }
-
- if coverdir, ok := integration.ResolveCoverageDir(); ok {
- err := integration.SetupTestscriptsCoverage(&p, coverdir)
- require.NoError(t, err)
- }
-
- err := integration.SetupGno(&p, t.TempDir())
- require.NoError(t, err)
-
- testscript.Run(t, p)
-}
-
func Test_parseGoBuildErrors(t *testing.T) {
t.Parallel()
diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go
index 338efa20fcc..82d5c69b08b 100644
--- a/gnovm/pkg/gnolang/go2gno.go
+++ b/gnovm/pkg/gnolang/go2gno.go
@@ -39,7 +39,9 @@ import (
"go/token"
"go/types"
"os"
+ "path"
"reflect"
+ "slices"
"strconv"
"strings"
@@ -499,6 +501,18 @@ type MemPackageGetter interface {
// If format is true, the code will be automatically updated with the
// formatted source code.
func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error {
+ return typeCheckMemPackage(mempkg, getter, false, format)
+}
+
+// TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage],
+// but allows re-declarations.
+//
+// Note: like TypeCheckMemPackage, this function ignores tests and filetests.
+func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error {
+ return typeCheckMemPackage(mempkg, getter, true, false)
+}
+
+func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error {
var errs error
imp := &gnoImporter{
getter: getter,
@@ -508,6 +522,7 @@ func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, form
errs = multierr.Append(errs, err)
},
},
+ allowRedefinitions: testing,
}
imp.cfg.Importer = imp
@@ -529,6 +544,9 @@ type gnoImporter struct {
getter MemPackageGetter
cache map[string]gnoImporterResult
cfg *types.Config
+
+ // allow symbol redefinitions? (test standard libraries)
+ allowRedefinitions bool
}
// Unused, but satisfies the Importer interface.
@@ -559,22 +577,39 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac
}
func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) {
+ // This map is used to allow for function re-definitions, which are allowed
+ // in Gno (testing context) but not in Go.
+ // This map links each function identifier with a closure to remove its
+ // associated declaration.
+ var delFunc map[string]func()
+ if g.allowRedefinitions {
+ delFunc = make(map[string]func())
+ }
+
fset := token.NewFileSet()
files := make([]*ast.File, 0, len(mpkg.Files))
var errs error
for _, file := range mpkg.Files {
+ // Ignore non-gno files.
+ // TODO: support filetest type checking. (should probably handle as each its
+ // own separate pkg, which should also be typechecked)
if !strings.HasSuffix(file.Name, ".gno") ||
- endsWith(file.Name, []string{"_test.gno", "_filetest.gno"}) {
- continue // skip spurious file.
+ strings.HasSuffix(file.Name, "_test.gno") ||
+ strings.HasSuffix(file.Name, "_filetest.gno") {
+ continue
}
const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution
- f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts)
+ f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts)
if err != nil {
errs = multierr.Append(errs, err)
continue
}
+ if delFunc != nil {
+ deleteOldIdents(delFunc, f)
+ }
+
// enforce formatting
if fmt {
var buf bytes.Buffer
@@ -595,6 +630,24 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*t
return g.cfg.Check(mpkg.Path, fset, files, nil)
}
+func deleteOldIdents(idents map[string]func(), f *ast.File) {
+ for _, decl := range f.Decls {
+ fd, ok := decl.(*ast.FuncDecl)
+ if !ok || fd.Recv != nil { // ignore methods
+ continue
+ }
+ if del := idents[fd.Name.Name]; del != nil {
+ del()
+ }
+ decl := decl
+ idents[fd.Name.Name] = func() {
+ // NOTE: cannot use the index as a file may contain multiple decls to be removed,
+ // so removing one would make all "later" indexes wrong.
+ f.Decls = slices.DeleteFunc(f.Decls, func(d ast.Decl) bool { return decl == d })
+ }
+ }
+}
+
//----------------------------------------
// utility methods
diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go
index b48c0742e6f..a497648dbc8 100644
--- a/gnovm/pkg/gnolang/machine.go
+++ b/gnovm/pkg/gnolang/machine.go
@@ -11,10 +11,9 @@ import (
"strings"
"sync"
- "github.com/gnolang/overflow"
-
"github.com/gnolang/gno/gnovm"
"github.com/gnolang/gno/tm2/pkg/errors"
+ "github.com/gnolang/gno/tm2/pkg/overflow"
"github.com/gnolang/gno/tm2/pkg/store"
)
diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go
index 8d3d6d8a2cc..0496d37ed72 100644
--- a/gnovm/pkg/gnolang/nodes.go
+++ b/gnovm/pkg/gnolang/nodes.go
@@ -2153,6 +2153,7 @@ type ValuePather interface {
// Utility
func (x *BasicLitExpr) GetString() string {
+ // Matches string literal parsing in go/constant.MakeFromLiteral.
str, err := strconv.Unquote(x.Value)
if err != nil {
panic("error in parsing string literal: " + err.Error())
diff --git a/gnovm/pkg/gnolang/op_eval.go b/gnovm/pkg/gnolang/op_eval.go
index 1beba1d6e3f..2aa13b21753 100644
--- a/gnovm/pkg/gnolang/op_eval.go
+++ b/gnovm/pkg/gnolang/op_eval.go
@@ -204,16 +204,14 @@ func (m *Machine) doOpEval() {
// and github.com/golang/go/issues/19921
panic("imaginaries are not supported")
case CHAR:
- cstr, err := strconv.Unquote(x.Value)
+ // Matching character literal parsing in go/constant.MakeFromLiteral.
+ val := x.Value
+ rne, _, _, err := strconv.UnquoteChar(val[1:len(val)-1], '\'')
if err != nil {
panic("error in parsing character literal: " + err.Error())
}
- runes := []rune(cstr)
- if len(runes) != 1 {
- panic(fmt.Sprintf("error in parsing character literal: 1 rune expected, but got %v (%s)", len(runes), cstr))
- }
tv := TypedValue{T: UntypedRuneType}
- tv.SetInt32(runes[0])
+ tv.SetInt32(rne)
m.PushValue(tv)
case STRING:
m.PushValue(TypedValue{
diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go
index a71e8a38b33..4ff182670cd 100644
--- a/gnovm/pkg/gnolang/preprocess.go
+++ b/gnovm/pkg/gnolang/preprocess.go
@@ -10,6 +10,7 @@ import (
"sync/atomic"
"github.com/gnolang/gno/tm2/pkg/errors"
+ tmstore "github.com/gnolang/gno/tm2/pkg/store"
)
const (
@@ -365,6 +366,12 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) {
func doRecover(stack []BlockNode, n Node) {
if r := recover(); r != nil {
+ // Catch the out-of-gas exception and throw it
+ if exp, ok := r.(tmstore.OutOfGasException); ok {
+ exp.Descriptor = fmt.Sprintf("in preprocess: %v", r)
+ panic(exp)
+ }
+
if _, ok := r.(*PreprocessError); ok {
// re-panic directly if this is a PreprocessError already.
panic(r)
@@ -743,7 +750,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
for i, cx := range n.Cases {
cx = Preprocess(
store, last, cx).(Expr)
- checkOrConvertType(store, last, &cx, tt, false) // #nosec G601
+ checkOrConvertType(store, last, n, &cx, tt, false) // #nosec G601
n.Cases[i] = cx
}
}
@@ -882,7 +889,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// Preprocess and convert tag if const.
if n.X != nil {
n.X = Preprocess(store, last, n.X).(Expr)
- convertIfConst(store, last, n.X)
+ convertIfConst(store, last, n, n.X)
}
}
return n, TRANS_CONTINUE
@@ -1102,10 +1109,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// First, convert untyped as necessary.
if !shouldSwapOnSpecificity(lcx.T, rcx.T) {
// convert n.Left to right type.
- checkOrConvertType(store, last, &n.Left, rcx.T, false)
+ checkOrConvertType(store, last, n, &n.Left, rcx.T, false)
} else {
// convert n.Right to left type.
- checkOrConvertType(store, last, &n.Right, lcx.T, false)
+ checkOrConvertType(store, last, n, &n.Right, lcx.T, false)
}
// Then, evaluate the expression.
cx := evalConst(store, last, n)
@@ -1125,7 +1132,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
rnt.String()))
}
// convert n.Left to pt type,
- checkOrConvertType(store, last, &n.Left, pt, false)
+ checkOrConvertType(store, last, n, &n.Left, pt, false)
// if check pass, convert n.Right to (gno) pt type,
rn := Expr(Call(pt.String(), n.Right))
// and convert result back.
@@ -1154,7 +1161,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
}
if !isUntyped(rt) { // right is typed
- checkOrConvertType(store, last, &n.Left, rt, false)
+ checkOrConvertType(store, last, n, &n.Left, rt, false)
} else {
if shouldSwapOnSpecificity(lt, rt) {
checkUntypedShiftExpr(n.Right)
@@ -1165,10 +1172,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
}
} else if lcx.T == nil { // LHS is nil.
// convert n.Left to typed-nil type.
- checkOrConvertType(store, last, &n.Left, rt, false)
+ checkOrConvertType(store, last, n, &n.Left, rt, false)
} else {
if isUntyped(rt) {
- checkOrConvertType(store, last, &n.Right, lt, false)
+ checkOrConvertType(store, last, n, &n.Right, lt, false)
}
}
} else if ric { // right is const, left is not
@@ -1186,7 +1193,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// convert n.Left to (gno) pt type,
ln := Expr(Call(pt.String(), n.Left))
// convert n.Right to pt type,
- checkOrConvertType(store, last, &n.Right, pt, false)
+ checkOrConvertType(store, last, n, &n.Right, pt, false)
// and convert result back.
tx := constType(n, lnt)
// reset/create n2 to preprocess left child.
@@ -1212,7 +1219,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
}
// both untyped, e.g. 1< float64.
// (const) untyped bigint -> int.
if !constConverted {
- convertConst(store, last, arg0, nil)
+ convertConst(store, last, n, arg0, nil)
}
// evaluate the new expression.
cx := evalConst(store, last, n)
@@ -1397,15 +1404,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
if isUntyped(at) {
switch arg0.Op {
case EQL, NEQ, LSS, GTR, LEQ, GEQ:
- assertAssignableTo(at, ct, false)
+ assertAssignableTo(n, at, ct, false)
break
default:
- checkOrConvertType(store, last, &n.Args[0], ct, false)
+ checkOrConvertType(store, last, n, &n.Args[0], ct, false)
}
}
case *UnaryExpr:
if isUntyped(at) {
- checkOrConvertType(store, last, &n.Args[0], ct, false)
+ checkOrConvertType(store, last, n, &n.Args[0], ct, false)
}
default:
// do nothing
@@ -1549,7 +1556,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
panic("should not happen")
}
// Specify function param/result generics.
- sft := ft.Specify(store, argTVs, isVarg)
+ sft := ft.Specify(store, n, argTVs, isVarg)
spts := sft.Params
srts := FieldTypeList(sft.Results).Types()
// If generics were specified, override attr
@@ -1575,12 +1582,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
for i, tv := range argTVs {
if hasVarg {
if (len(spts) - 1) <= i {
- assertAssignableTo(tv.T, spts[len(spts)-1].Type.Elem(), true)
+ assertAssignableTo(n, tv.T, spts[len(spts)-1].Type.Elem(), true)
} else {
- assertAssignableTo(tv.T, spts[i].Type, true)
+ assertAssignableTo(n, tv.T, spts[i].Type, true)
}
} else {
- assertAssignableTo(tv.T, spts[i].Type, true)
+ assertAssignableTo(n, tv.T, spts[i].Type, true)
}
}
} else {
@@ -1591,16 +1598,16 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
if len(spts) <= i {
panic("expected final vargs slice but got many")
}
- checkOrConvertType(store, last, &n.Args[i], spts[i].Type, true)
+ checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true)
} else {
- checkOrConvertType(store, last, &n.Args[i],
+ checkOrConvertType(store, last, n, &n.Args[i],
spts[len(spts)-1].Type.Elem(), true)
}
} else {
- checkOrConvertType(store, last, &n.Args[i], spts[i].Type, true)
+ checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true)
}
} else {
- checkOrConvertType(store, last, &n.Args[i], spts[i].Type, true)
+ checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true)
}
}
}
@@ -1621,10 +1628,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
case StringKind, ArrayKind, SliceKind:
// Replace const index with int *ConstExpr,
// or if not const, assert integer type..
- checkOrConvertIntegerKind(store, last, n.Index)
+ checkOrConvertIntegerKind(store, last, n, n.Index)
case MapKind:
mt := baseOf(gnoTypeOf(store, dt)).(*MapType)
- checkOrConvertType(store, last, &n.Index, mt.Key, false)
+ checkOrConvertType(store, last, n, &n.Index, mt.Key, false)
default:
panic(fmt.Sprintf(
"unexpected index base kind for type %s",
@@ -1635,15 +1642,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
case *SliceExpr:
// Replace const L/H/M with int *ConstExpr,
// or if not const, assert integer type..
- checkOrConvertIntegerKind(store, last, n.Low)
- checkOrConvertIntegerKind(store, last, n.High)
- checkOrConvertIntegerKind(store, last, n.Max)
+ checkOrConvertIntegerKind(store, last, n, n.Low)
+ checkOrConvertIntegerKind(store, last, n, n.High)
+ checkOrConvertIntegerKind(store, last, n, n.Max)
// if n.X is untyped, convert to corresponding type
t := evalStaticTypeOf(store, last, n.X)
if isUntyped(t) {
dt := defaultTypeOf(t)
- checkOrConvertType(store, last, &n.X, dt, false)
+ checkOrConvertType(store, last, n, &n.X, dt, false)
}
// TRANS_LEAVE -----------------------
@@ -1722,28 +1729,28 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
key := n.Elts[i].Key.(*NameExpr).Name
path := cclt.GetPathForName(key)
ft := cclt.GetStaticTypeOfAt(path)
- checkOrConvertType(store, last, &n.Elts[i].Value, ft, false)
+ checkOrConvertType(store, last, n, &n.Elts[i].Value, ft, false)
}
} else {
for i := 0; i < len(n.Elts); i++ {
ft := cclt.Fields[i].Type
- checkOrConvertType(store, last, &n.Elts[i].Value, ft, false)
+ checkOrConvertType(store, last, n, &n.Elts[i].Value, ft, false)
}
}
case *ArrayType:
for i := 0; i < len(n.Elts); i++ {
- convertType(store, last, &n.Elts[i].Key, IntType)
- checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false)
+ convertType(store, last, n, &n.Elts[i].Key, IntType)
+ checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Elt, false)
}
case *SliceType:
for i := 0; i < len(n.Elts); i++ {
- convertType(store, last, &n.Elts[i].Key, IntType)
- checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false)
+ convertType(store, last, n, &n.Elts[i].Key, IntType)
+ checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Elt, false)
}
case *MapType:
for i := 0; i < len(n.Elts); i++ {
- checkOrConvertType(store, last, &n.Elts[i].Key, cclt.Key, false)
- checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Value, false)
+ checkOrConvertType(store, last, n, &n.Elts[i].Key, cclt.Key, false)
+ checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Value, false)
}
case *NativeType:
clt = cclt.GnoType(store)
@@ -1943,7 +1950,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// TRANS_LEAVE -----------------------
case *FieldTypeExpr:
// Replace const Tag with default *ConstExpr.
- convertIfConst(store, last, n.Tag)
+ convertIfConst(store, last, n, n.Tag)
// TRANS_LEAVE -----------------------
case *ArrayTypeExpr:
@@ -1952,7 +1959,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
} else {
// Replace const Len with int *ConstExpr.
cx := evalConst(store, last, n.Len)
- convertConst(store, last, cx, IntType)
+ convertConst(store, last, n, cx, IntType)
n.Len = cx
}
// NOTE: For all TypeExprs, the node is not replaced
@@ -1993,7 +2000,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// Rhs consts become default *ConstExprs.
for _, rx := range n.Rhs {
// NOTE: does nothing if rx is "nil".
- convertIfConst(store, last, rx)
+ convertIfConst(store, last, n, rx)
}
nameExprs := make(NameExprs, len(n.Lhs))
@@ -2001,7 +2008,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
nameExprs[i] = *n.Lhs[i].(*NameExpr)
}
- defineOrDecl(store, last, false, nameExprs, nil, n.Rhs)
+ defineOrDecl(store, last, n, false, nameExprs, nil, n.Rhs)
} else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.)
// NOTE: Keep in sync with DEFINE above.
if len(n.Lhs) > len(n.Rhs) {
@@ -2090,11 +2097,11 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
} else { // len(Lhs) == len(Rhs)
if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN {
// Special case if shift assign <<= or >>=.
- convertType(store, last, &n.Rhs[0], UintType)
+ convertType(store, last, n, &n.Rhs[0], UintType)
} else if n.Op == ADD_ASSIGN || n.Op == SUB_ASSIGN || n.Op == MUL_ASSIGN || n.Op == QUO_ASSIGN || n.Op == REM_ASSIGN {
// e.g. a += b, single value for lhs and rhs,
lt := evalStaticTypeOf(store, last, n.Lhs[0])
- checkOrConvertType(store, last, &n.Rhs[0], lt, true)
+ checkOrConvertType(store, last, n, &n.Rhs[0], lt, true)
} else { // all else, like BAND_ASSIGN, etc
// General case: a, b = x, y.
for i, lx := range n.Lhs {
@@ -2104,7 +2111,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
}
// if lt is interface, nothing will happen
- checkOrConvertType(store, last, &n.Rhs[i], lt, true)
+ checkOrConvertType(store, last, n, &n.Rhs[i], lt, true)
}
}
}
@@ -2181,12 +2188,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// TRANS_LEAVE -----------------------
case *ForStmt:
// Cond consts become bool *ConstExprs.
- checkOrConvertBoolKind(store, last, n.Cond)
+ checkOrConvertBoolKind(store, last, n, n.Cond)
// TRANS_LEAVE -----------------------
case *IfStmt:
// Cond consts become bool *ConstExprs.
- checkOrConvertBoolKind(store, last, n.Cond)
+ checkOrConvertBoolKind(store, last, n, n.Cond)
// TRANS_LEAVE -----------------------
case *RangeStmt:
@@ -2242,7 +2249,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// XXX how to deal?
panic("not yet implemented")
} else {
- checkOrConvertType(store, last, &n.Results[i], rt, false)
+ checkOrConvertType(store, last, n, &n.Results[i], rt, false)
}
}
}
@@ -2250,7 +2257,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// TRANS_LEAVE -----------------------
case *SendStmt:
// Value consts become default *ConstExprs.
- checkOrConvertType(store, last, &n.Value, nil, false)
+ checkOrConvertType(store, last, n, &n.Value, nil, false)
// TRANS_LEAVE -----------------------
case *SelectCaseStmt:
@@ -2303,7 +2310,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
// runDeclaration(), as this uses OpStaticTypeOf.
}
- defineOrDecl(store, last, n.Const, n.NameExprs, n.Type, n.Values)
+ defineOrDecl(store, last, n, n.Const, n.NameExprs, n.Type, n.Values)
// TODO make note of constance in static block for
// future use, or consider "const paths". set as
@@ -2383,6 +2390,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node {
func defineOrDecl(
store Store,
bn BlockNode,
+ n Node,
isConst bool,
nameExprs []NameExpr,
typeExpr Expr,
@@ -2399,9 +2407,9 @@ func defineOrDecl(
tvs := make([]TypedValue, numNames)
if numVals == 1 && numNames > 1 {
- parseMultipleAssignFromOneExpr(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0])
+ parseMultipleAssignFromOneExpr(store, bn, n, sts, tvs, nameExprs, typeExpr, valueExprs[0])
} else {
- parseAssignFromExprList(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs)
+ parseAssignFromExprList(store, bn, n, sts, tvs, isConst, nameExprs, typeExpr, valueExprs)
}
node := skipFile(bn)
@@ -2420,10 +2428,11 @@ func defineOrDecl(
// parseAssignFromExprList parses assignment to multiple variables from a list of expressions.
// This function will alter the value of sts, tvs.
func parseAssignFromExprList(
- sts []Type,
- tvs []TypedValue,
store Store,
bn BlockNode,
+ n Node,
+ sts []Type,
+ tvs []TypedValue,
isConst bool,
nameExprs []NameExpr,
typeExpr Expr,
@@ -2450,7 +2459,7 @@ func parseAssignFromExprList(
}
// Convert if const to nt.
for i := range valueExprs {
- checkOrConvertType(store, bn, &valueExprs[i], nt, false)
+ checkOrConvertType(store, bn, n, &valueExprs[i], nt, false)
}
} else if isConst {
// Derive static type from values.
@@ -2462,10 +2471,10 @@ func parseAssignFromExprList(
// Convert n.Value to default type.
for i, vx := range valueExprs {
if cx, ok := vx.(*ConstExpr); ok {
- convertConst(store, bn, cx, nil)
+ convertConst(store, bn, n, cx, nil)
// convertIfConst(store, last, vx)
} else {
- checkOrConvertType(store, bn, &vx, nil, false)
+ checkOrConvertType(store, bn, n, &vx, nil, false)
}
vt := evalStaticTypeOf(store, bn, vx)
sts[i] = vt
@@ -2506,10 +2515,11 @@ func parseAssignFromExprList(
// - a, b := n.(T)
// - a, b := n[i], where n is a map
func parseMultipleAssignFromOneExpr(
- sts []Type,
- tvs []TypedValue,
store Store,
bn BlockNode,
+ n Node,
+ sts []Type,
+ tvs []TypedValue,
nameExprs []NameExpr,
typeExpr Expr,
valueExpr Expr,
@@ -2567,7 +2577,7 @@ func parseMultipleAssignFromOneExpr(
if st != nil {
tt := tuple.Elts[i]
- if checkAssignableTo(tt, st, false) != nil {
+ if checkAssignableTo(n, tt, st, false) != nil {
panic(
fmt.Sprintf(
"cannot use %v (value of type %s) as %s value in assignment",
@@ -3491,14 +3501,14 @@ func isConstType(x Expr) bool {
}
// check before convert type
-func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative bool) {
+func checkOrConvertType(store Store, last BlockNode, n Node, x *Expr, t Type, autoNative bool) {
if debug {
debug.Printf("checkOrConvertType, *x: %v:, t:%v \n", *x, t)
}
if cx, ok := (*x).(*ConstExpr); ok {
if _, ok := t.(*NativeType); !ok { // not native type, refer to time4_native.gno.
// e.g. int(1) == int8(1)
- assertAssignableTo(cx.T, t, autoNative)
+ assertAssignableTo(n, cx.T, t, autoNative)
}
} else if bx, ok := (*x).(*BinaryExpr); ok && (bx.Op == SHL || bx.Op == SHR) {
xt := evalStaticTypeOf(store, last, *x)
@@ -3507,22 +3517,22 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative
}
if isUntyped(xt) {
// check assignable first, see: types/shift_b6.gno
- assertAssignableTo(xt, t, autoNative)
+ assertAssignableTo(n, xt, t, autoNative)
if t == nil || t.Kind() == InterfaceKind {
t = defaultTypeOf(xt)
}
bx.assertShiftExprCompatible2(t)
- checkOrConvertType(store, last, &bx.Left, t, autoNative)
+ checkOrConvertType(store, last, n, &bx.Left, t, autoNative)
} else {
- assertAssignableTo(xt, t, autoNative)
+ assertAssignableTo(n, xt, t, autoNative)
}
return
} else if *x != nil {
xt := evalStaticTypeOf(store, last, *x)
if t != nil {
- assertAssignableTo(xt, t, autoNative)
+ assertAssignableTo(n, xt, t, autoNative)
}
if isUntyped(xt) {
// Push type into expr if qualifying binary expr.
@@ -3534,8 +3544,8 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative
rt := evalStaticTypeOf(store, last, bx.Right)
if t != nil {
// push t into bx.Left and bx.Right
- checkOrConvertType(store, last, &bx.Left, t, autoNative)
- checkOrConvertType(store, last, &bx.Right, t, autoNative)
+ checkOrConvertType(store, last, n, &bx.Left, t, autoNative)
+ checkOrConvertType(store, last, n, &bx.Right, t, autoNative)
return
} else {
if shouldSwapOnSpecificity(lt, rt) {
@@ -3546,11 +3556,11 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative
// without a specific context type, '1.0< + (const (undefined)) (mismatched types int and untyped nil)
diff --git a/gnovm/tests/files/assign38.gno b/gnovm/tests/files/assign38.gno
new file mode 100644
index 00000000000..5ef3549ccf6
--- /dev/null
+++ b/gnovm/tests/files/assign38.gno
@@ -0,0 +1,10 @@
+package main
+
+func main() {
+ a := 1
+ a = nil
+ println(a)
+}
+
+// Error:
+// main/files/assign38.gno:5:2: cannot use nil as int value in assignment
diff --git a/gnovm/tests/files/block0.gno b/gnovm/tests/files/block0.gno
new file mode 100644
index 00000000000..b6d554ce500
--- /dev/null
+++ b/gnovm/tests/files/block0.gno
@@ -0,0 +1,14 @@
+package main
+
+func foo() int {
+ {
+ return 1
+ }
+}
+
+func main() {
+ println(foo())
+}
+
+// Output:
+// 1
diff --git a/gnovm/tests/files/fun28.gno b/gnovm/tests/files/fun28.gno
new file mode 100644
index 00000000000..cf969f9f34b
--- /dev/null
+++ b/gnovm/tests/files/fun28.gno
@@ -0,0 +1,10 @@
+package main
+
+func f(i int) {}
+
+func main() {
+ f(nil)
+}
+
+// Error:
+// main/files/fun28.gno:6:2: cannot use nil as int value in argument to f
diff --git a/gnovm/tests/files/rune3.gno b/gnovm/tests/files/rune3.gno
new file mode 100644
index 00000000000..e848565e3a4
--- /dev/null
+++ b/gnovm/tests/files/rune3.gno
@@ -0,0 +1,10 @@
+package main
+
+const overflow = '\xff'
+
+func main() {
+ println(overflow)
+}
+
+// Output:
+// 255
diff --git a/gnovm/tests/files/slice3.gno b/gnovm/tests/files/slice3.gno
new file mode 100644
index 00000000000..1132da01420
--- /dev/null
+++ b/gnovm/tests/files/slice3.gno
@@ -0,0 +1,9 @@
+package main
+
+func main() {
+ i := []string{nil}
+ println(i)
+}
+
+// Error:
+// main/files/slice3.gno:4:7: cannot use nil as string value in array, slice literal or map literal
diff --git a/gnovm/tests/files/type40.gno b/gnovm/tests/files/type40.gno
index 65210798007..fe312e220e0 100644
--- a/gnovm/tests/files/type40.gno
+++ b/gnovm/tests/files/type40.gno
@@ -43,4 +43,4 @@ func main() {
// 5
// 6
// 7
-// yo
\ No newline at end of file
+// yo
diff --git a/gnovm/tests/files/type41.gno b/gnovm/tests/files/type41.gno
new file mode 100644
index 00000000000..ea1a3b1df24
--- /dev/null
+++ b/gnovm/tests/files/type41.gno
@@ -0,0 +1,9 @@
+package main
+
+type A nil
+
+func main() {
+}
+
+// Error:
+// main/files/type41.gno:3:6: nil is not a type
diff --git a/gnovm/tests/files/var35.gno b/gnovm/tests/files/var35.gno
new file mode 100644
index 00000000000..87b1cc68590
--- /dev/null
+++ b/gnovm/tests/files/var35.gno
@@ -0,0 +1,8 @@
+package main
+
+func main() {
+ var i int = nil
+}
+
+// Error:
+// main/files/var35.gno:4:6: cannot use nil as int value in variable declaration
diff --git a/gnovm/tests/integ/typecheck_missing_return/gno.mod b/gnovm/tests/integ/typecheck_missing_return/gno.mod
new file mode 100644
index 00000000000..3eaaa374994
--- /dev/null
+++ b/gnovm/tests/integ/typecheck_missing_return/gno.mod
@@ -0,0 +1 @@
+module gno.land/p/integ/valid
diff --git a/gnovm/tests/integ/typecheck_missing_return/main.gno b/gnovm/tests/integ/typecheck_missing_return/main.gno
new file mode 100644
index 00000000000..5d6e547097c
--- /dev/null
+++ b/gnovm/tests/integ/typecheck_missing_return/main.gno
@@ -0,0 +1,5 @@
+package valid
+
+func Hello() int {
+ // no return
+}
diff --git a/go.mod b/go.mod
index f73ba1926e6..f389e60b988 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,6 @@ require (
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
github.com/fortytw2/leaktest v1.3.0
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216
github.com/google/gofuzz v1.2.0
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
diff --git a/go.sum b/go.sum
index 78d60eeea90..b987535607e 100644
--- a/go.sum
+++ b/go.sum
@@ -51,8 +51,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod
index 5de1d3c2974..30a6f23b458 100644
--- a/misc/autocounterd/go.mod
+++ b/misc/autocounterd/go.mod
@@ -14,7 +14,6 @@ require (
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum
index b34cbde0c00..5d624ca18cb 100644
--- a/misc/autocounterd/go.sum
+++ b/misc/autocounterd/go.sum
@@ -50,8 +50,6 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/misc/loop/go.mod b/misc/loop/go.mod
index a6bbdad3c82..af7783e57bb 100644
--- a/misc/loop/go.mod
+++ b/misc/loop/go.mod
@@ -8,7 +8,7 @@ require (
github.com/docker/docker v24.0.7+incompatible
github.com/docker/go-connections v0.4.0
github.com/gnolang/gno v0.1.0-nightly.20240627
- github.com/gnolang/tx-archive v0.4.0
+ github.com/gnolang/tx-archive v0.4.2
github.com/prometheus/client_golang v1.17.0
github.com/sirupsen/logrus v1.9.3
)
@@ -29,7 +29,6 @@ require (
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
- github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
diff --git a/misc/loop/go.sum b/misc/loop/go.sum
index 740cc629a21..0d235f2cfb1 100644
--- a/misc/loop/go.sum
+++ b/misc/loop/go.sum
@@ -68,10 +68,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
-github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
-github.com/gnolang/tx-archive v0.4.0 h1:+1Rgo0U0HjLQLq/xqeGdJwtAzo9xWj09t1oZLvrL3bU=
-github.com/gnolang/tx-archive v0.4.0/go.mod h1:seKHGnvxUnDgH/mSsCEdwG0dHY/FrpbUm6Hd0+KMd9w=
+github.com/gnolang/tx-archive v0.4.2 h1:xBBqLLKY9riv9yxpQgVhItCWxIji2rX6xNFmCY1cEOQ=
+github.com/gnolang/tx-archive v0.4.2/go.mod h1:AGUBGO+DCLuKL80a1GJRnpcJ5gxVd9L4jEJXQB9uXp4=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -258,8 +256,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
-golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/tm2/pkg/overflow/README.md b/tm2/pkg/overflow/README.md
new file mode 100644
index 00000000000..55a9ba4c327
--- /dev/null
+++ b/tm2/pkg/overflow/README.md
@@ -0,0 +1,66 @@
+# overflow
+
+Check for int/int8/int16/int64/int32 integer overflow in Golang arithmetic.
+
+Forked from https://github.com/JohnCGriffin/overflow
+
+### Install
+```
+go get github.com/johncgriffin/overflow
+```
+Note that because Go has no template types, the majority of repetitive code is
+generated by overflow_template.sh. If you have to change an
+algorithm, change it there and regenerate the Go code via:
+```
+go generate
+```
+### Synopsis
+
+```
+package main
+
+import "fmt"
+import "math"
+import "github.com/JohnCGriffin/overflow"
+
+func main() {
+
+ addend := math.MaxInt64 - 5
+
+ for i := 0; i < 10; i++ {
+ sum, ok := overflow.Add(addend, i)
+ fmt.Printf("%v+%v -> (%v,%v)\n",
+ addend, i, sum, ok)
+ }
+
+}
+```
+yields the output
+```
+9223372036854775802+0 -> (9223372036854775802,true)
+9223372036854775802+1 -> (9223372036854775803,true)
+9223372036854775802+2 -> (9223372036854775804,true)
+9223372036854775802+3 -> (9223372036854775805,true)
+9223372036854775802+4 -> (9223372036854775806,true)
+9223372036854775802+5 -> (9223372036854775807,true)
+9223372036854775802+6 -> (0,false)
+9223372036854775802+7 -> (0,false)
+9223372036854775802+8 -> (0,false)
+9223372036854775802+9 -> (0,false)
+```
+
+For int, int64, and int32 types, provide Add, Add32, Add64, Sub, Sub32, Sub64, etc.
+Unsigned types not covered at the moment, but such additions are welcome.
+
+### Stay calm and panic
+
+There's a good case to be made that a panic is an unidiomatic but proper response. Iff you
+believe that there's no valid way to continue your program after math goes wayward, you can
+use the easier Addp, Mulp, Subp, and Divp versions which return the normal result or panic.
+
+
+
+
+
+
+
diff --git a/tm2/pkg/overflow/overflow.go b/tm2/pkg/overflow/overflow.go
new file mode 100644
index 00000000000..b476ea5776e
--- /dev/null
+++ b/tm2/pkg/overflow/overflow.go
@@ -0,0 +1,131 @@
+/*
+Package overflow offers overflow-checked integer arithmetic operations
+for int, int32, and int64. Each of the operations returns a
+result,bool combination. This was prompted by the need to know when
+to flow into higher precision types from the math.big library.
+
+For instance, assuing a 64 bit machine:
+
+10 + 20 -> 30
+int(math.MaxInt64) + 1 -> -9223372036854775808
+
+whereas
+
+overflow.Add(10,20) -> (30, true)
+overflow.Add(math.MaxInt64,1) -> (0, false)
+
+Add, Sub, Mul, Div are for int. Add64, Add32, etc. are specifically sized.
+
+If anybody wishes an unsigned version, submit a pull request for code
+and new tests.
+*/
+package overflow
+
+//go:generate ./overflow_template.sh
+
+import "math"
+
+func _is64Bit() bool {
+ maxU32 := uint(math.MaxUint32)
+ return ((maxU32 << 1) >> 1) == maxU32
+}
+
+/********** PARTIAL TEST COVERAGE FROM HERE DOWN *************
+
+The only way that I could see to do this is a combination of
+my normal 64 bit system and a GopherJS running on Node. My
+understanding is that its ints are 32 bit.
+
+So, FEEL FREE to carefully review the code visually.
+
+*************************************************************/
+
+// Unspecified size, i.e. normal signed int
+
+// Add sums two ints, returning the result and a boolean status.
+func Add(a, b int) (int, bool) {
+ if _is64Bit() {
+ r64, ok := Add64(int64(a), int64(b))
+ return int(r64), ok
+ }
+ r32, ok := Add32(int32(a), int32(b))
+ return int(r32), ok
+}
+
+// Sub returns the difference of two ints and a boolean status.
+func Sub(a, b int) (int, bool) {
+ if _is64Bit() {
+ r64, ok := Sub64(int64(a), int64(b))
+ return int(r64), ok
+ }
+ r32, ok := Sub32(int32(a), int32(b))
+ return int(r32), ok
+}
+
+// Mul returns the product of two ints and a boolean status.
+func Mul(a, b int) (int, bool) {
+ if _is64Bit() {
+ r64, ok := Mul64(int64(a), int64(b))
+ return int(r64), ok
+ }
+ r32, ok := Mul32(int32(a), int32(b))
+ return int(r32), ok
+}
+
+// Div returns the quotient of two ints and a boolean status
+func Div(a, b int) (int, bool) {
+ if _is64Bit() {
+ r64, ok := Div64(int64(a), int64(b))
+ return int(r64), ok
+ }
+ r32, ok := Div32(int32(a), int32(b))
+ return int(r32), ok
+}
+
+// Quotient returns the quotient, remainder and status of two ints
+func Quotient(a, b int) (int, int, bool) {
+ if _is64Bit() {
+ q64, r64, ok := Quotient64(int64(a), int64(b))
+ return int(q64), int(r64), ok
+ }
+ q32, r32, ok := Quotient32(int32(a), int32(b))
+ return int(q32), int(r32), ok
+}
+
+/************* Panic versions for int ****************/
+
+// Addp returns the sum of two ints, panicking on overflow
+func Addp(a, b int) int {
+ r, ok := Add(a, b)
+ if !ok {
+ panic("addition overflow")
+ }
+ return r
+}
+
+// Subp returns the difference of two ints, panicking on overflow.
+func Subp(a, b int) int {
+ r, ok := Sub(a, b)
+ if !ok {
+ panic("subtraction overflow")
+ }
+ return r
+}
+
+// Mulp returns the product of two ints, panicking on overflow.
+func Mulp(a, b int) int {
+ r, ok := Mul(a, b)
+ if !ok {
+ panic("multiplication overflow")
+ }
+ return r
+}
+
+// Divp returns the quotient of two ints, panicking on overflow.
+func Divp(a, b int) int {
+ r, ok := Div(a, b)
+ if !ok {
+ panic("division failure")
+ }
+ return r
+}
diff --git a/tm2/pkg/overflow/overflow_impl.go b/tm2/pkg/overflow/overflow_impl.go
new file mode 100644
index 00000000000..a9a90c43835
--- /dev/null
+++ b/tm2/pkg/overflow/overflow_impl.go
@@ -0,0 +1,360 @@
+package overflow
+
+// This is generated code, created by overflow_template.sh executed
+// by "go generate"
+
+// Add8 performs + operation on two int8 operands
+// returning a result and status
+func Add8(a, b int8) (int8, bool) {
+ c := a + b
+ if (c > a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Add8p is the unchecked panicing version of Add8
+func Add8p(a, b int8) int8 {
+ r, ok := Add8(a, b)
+ if !ok {
+ panic("addition overflow")
+ }
+ return r
+}
+
+// Sub8 performs - operation on two int8 operands
+// returning a result and status
+func Sub8(a, b int8) (int8, bool) {
+ c := a - b
+ if (c < a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Sub8p is the unchecked panicing version of Sub8
+func Sub8p(a, b int8) int8 {
+ r, ok := Sub8(a, b)
+ if !ok {
+ panic("subtraction overflow")
+ }
+ return r
+}
+
+// Mul8 performs * operation on two int8 operands
+// returning a result and status
+func Mul8(a, b int8) (int8, bool) {
+ if a == 0 || b == 0 {
+ return 0, true
+ }
+ c := a * b
+ if (c < 0) == ((a < 0) != (b < 0)) {
+ if c/b == a {
+ return c, true
+ }
+ }
+ return c, false
+}
+
+// Mul8p is the unchecked panicing version of Mul8
+func Mul8p(a, b int8) int8 {
+ r, ok := Mul8(a, b)
+ if !ok {
+ panic("multiplication overflow")
+ }
+ return r
+}
+
+// Div8 performs / operation on two int8 operands
+// returning a result and status
+func Div8(a, b int8) (int8, bool) {
+ q, _, ok := Quotient8(a, b)
+ return q, ok
+}
+
+// Div8p is the unchecked panicing version of Div8
+func Div8p(a, b int8) int8 {
+ r, ok := Div8(a, b)
+ if !ok {
+ panic("division failure")
+ }
+ return r
+}
+
+// Quotient8 performs + operation on two int8 operands
+// returning a quotient, a remainder and status
+func Quotient8(a, b int8) (int8, int8, bool) {
+ if b == 0 {
+ return 0, 0, false
+ }
+ c := a / b
+ status := (c < 0) == ((a < 0) != (b < 0))
+ return c, a % b, status
+}
+
+// Add16 performs + operation on two int16 operands
+// returning a result and status
+func Add16(a, b int16) (int16, bool) {
+ c := a + b
+ if (c > a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Add16p is the unchecked panicing version of Add16
+func Add16p(a, b int16) int16 {
+ r, ok := Add16(a, b)
+ if !ok {
+ panic("addition overflow")
+ }
+ return r
+}
+
+// Sub16 performs - operation on two int16 operands
+// returning a result and status
+func Sub16(a, b int16) (int16, bool) {
+ c := a - b
+ if (c < a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Sub16p is the unchecked panicing version of Sub16
+func Sub16p(a, b int16) int16 {
+ r, ok := Sub16(a, b)
+ if !ok {
+ panic("subtraction overflow")
+ }
+ return r
+}
+
+// Mul16 performs * operation on two int16 operands
+// returning a result and status
+func Mul16(a, b int16) (int16, bool) {
+ if a == 0 || b == 0 {
+ return 0, true
+ }
+ c := a * b
+ if (c < 0) == ((a < 0) != (b < 0)) {
+ if c/b == a {
+ return c, true
+ }
+ }
+ return c, false
+}
+
+// Mul16p is the unchecked panicing version of Mul16
+func Mul16p(a, b int16) int16 {
+ r, ok := Mul16(a, b)
+ if !ok {
+ panic("multiplication overflow")
+ }
+ return r
+}
+
+// Div16 performs / operation on two int16 operands
+// returning a result and status
+func Div16(a, b int16) (int16, bool) {
+ q, _, ok := Quotient16(a, b)
+ return q, ok
+}
+
+// Div16p is the unchecked panicing version of Div16
+func Div16p(a, b int16) int16 {
+ r, ok := Div16(a, b)
+ if !ok {
+ panic("division failure")
+ }
+ return r
+}
+
+// Quotient16 performs + operation on two int16 operands
+// returning a quotient, a remainder and status
+func Quotient16(a, b int16) (int16, int16, bool) {
+ if b == 0 {
+ return 0, 0, false
+ }
+ c := a / b
+ status := (c < 0) == ((a < 0) != (b < 0))
+ return c, a % b, status
+}
+
+// Add32 performs + operation on two int32 operands
+// returning a result and status
+func Add32(a, b int32) (int32, bool) {
+ c := a + b
+ if (c > a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Add32p is the unchecked panicing version of Add32
+func Add32p(a, b int32) int32 {
+ r, ok := Add32(a, b)
+ if !ok {
+ panic("addition overflow")
+ }
+ return r
+}
+
+// Sub32 performs - operation on two int32 operands
+// returning a result and status
+func Sub32(a, b int32) (int32, bool) {
+ c := a - b
+ if (c < a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Sub32p is the unchecked panicing version of Sub32
+func Sub32p(a, b int32) int32 {
+ r, ok := Sub32(a, b)
+ if !ok {
+ panic("subtraction overflow")
+ }
+ return r
+}
+
+// Mul32 performs * operation on two int32 operands
+// returning a result and status
+func Mul32(a, b int32) (int32, bool) {
+ if a == 0 || b == 0 {
+ return 0, true
+ }
+ c := a * b
+ if (c < 0) == ((a < 0) != (b < 0)) {
+ if c/b == a {
+ return c, true
+ }
+ }
+ return c, false
+}
+
+// Mul32p is the unchecked panicing version of Mul32
+func Mul32p(a, b int32) int32 {
+ r, ok := Mul32(a, b)
+ if !ok {
+ panic("multiplication overflow")
+ }
+ return r
+}
+
+// Div32 performs / operation on two int32 operands
+// returning a result and status
+func Div32(a, b int32) (int32, bool) {
+ q, _, ok := Quotient32(a, b)
+ return q, ok
+}
+
+// Div32p is the unchecked panicing version of Div32
+func Div32p(a, b int32) int32 {
+ r, ok := Div32(a, b)
+ if !ok {
+ panic("division failure")
+ }
+ return r
+}
+
+// Quotient32 performs + operation on two int32 operands
+// returning a quotient, a remainder and status
+func Quotient32(a, b int32) (int32, int32, bool) {
+ if b == 0 {
+ return 0, 0, false
+ }
+ c := a / b
+ status := (c < 0) == ((a < 0) != (b < 0))
+ return c, a % b, status
+}
+
+// Add64 performs + operation on two int64 operands
+// returning a result and status
+func Add64(a, b int64) (int64, bool) {
+ c := a + b
+ if (c > a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Add64p is the unchecked panicing version of Add64
+func Add64p(a, b int64) int64 {
+ r, ok := Add64(a, b)
+ if !ok {
+ panic("addition overflow")
+ }
+ return r
+}
+
+// Sub64 performs - operation on two int64 operands
+// returning a result and status
+func Sub64(a, b int64) (int64, bool) {
+ c := a - b
+ if (c < a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Sub64p is the unchecked panicing version of Sub64
+func Sub64p(a, b int64) int64 {
+ r, ok := Sub64(a, b)
+ if !ok {
+ panic("subtraction overflow")
+ }
+ return r
+}
+
+// Mul64 performs * operation on two int64 operands
+// returning a result and status
+func Mul64(a, b int64) (int64, bool) {
+ if a == 0 || b == 0 {
+ return 0, true
+ }
+ c := a * b
+ if (c < 0) == ((a < 0) != (b < 0)) {
+ if c/b == a {
+ return c, true
+ }
+ }
+ return c, false
+}
+
+// Mul64p is the unchecked panicing version of Mul64
+func Mul64p(a, b int64) int64 {
+ r, ok := Mul64(a, b)
+ if !ok {
+ panic("multiplication overflow")
+ }
+ return r
+}
+
+// Div64 performs / operation on two int64 operands
+// returning a result and status
+func Div64(a, b int64) (int64, bool) {
+ q, _, ok := Quotient64(a, b)
+ return q, ok
+}
+
+// Div64p is the unchecked panicing version of Div64
+func Div64p(a, b int64) int64 {
+ r, ok := Div64(a, b)
+ if !ok {
+ panic("division failure")
+ }
+ return r
+}
+
+// Quotient64 performs + operation on two int64 operands
+// returning a quotient, a remainder and status
+func Quotient64(a, b int64) (int64, int64, bool) {
+ if b == 0 {
+ return 0, 0, false
+ }
+ c := a / b
+ status := (c < 0) == ((a < 0) != (b < 0))
+ return c, a % b, status
+}
diff --git a/tm2/pkg/overflow/overflow_template.sh b/tm2/pkg/overflow/overflow_template.sh
new file mode 100755
index 00000000000..a2a85f2c581
--- /dev/null
+++ b/tm2/pkg/overflow/overflow_template.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+
+exec > overflow_impl.go
+
+echo "package overflow
+
+// This is generated code, created by overflow_template.sh executed
+// by \"go generate\"
+
+"
+
+
+for SIZE in 8 16 32 64
+do
+echo "
+
+// Add${SIZE} performs + operation on two int${SIZE} operands
+// returning a result and status
+func Add${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) {
+ c := a + b
+ if (c > a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Add${SIZE}p is the unchecked panicing version of Add${SIZE}
+func Add${SIZE}p(a, b int${SIZE}) int${SIZE} {
+ r, ok := Add${SIZE}(a, b)
+ if !ok {
+ panic(\"addition overflow\")
+ }
+ return r
+}
+
+
+// Sub${SIZE} performs - operation on two int${SIZE} operands
+// returning a result and status
+func Sub${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) {
+ c := a - b
+ if (c < a) == (b > 0) {
+ return c, true
+ }
+ return c, false
+}
+
+// Sub${SIZE}p is the unchecked panicing version of Sub${SIZE}
+func Sub${SIZE}p(a, b int${SIZE}) int${SIZE} {
+ r, ok := Sub${SIZE}(a, b)
+ if !ok {
+ panic(\"subtraction overflow\")
+ }
+ return r
+}
+
+
+// Mul${SIZE} performs * operation on two int${SIZE} operands
+// returning a result and status
+func Mul${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) {
+ if a == 0 || b == 0 {
+ return 0, true
+ }
+ c := a * b
+ if (c < 0) == ((a < 0) != (b < 0)) {
+ if c/b == a {
+ return c, true
+ }
+ }
+ return c, false
+}
+
+// Mul${SIZE}p is the unchecked panicing version of Mul${SIZE}
+func Mul${SIZE}p(a, b int${SIZE}) int${SIZE} {
+ r, ok := Mul${SIZE}(a, b)
+ if !ok {
+ panic(\"multiplication overflow\")
+ }
+ return r
+}
+
+
+
+// Div${SIZE} performs / operation on two int${SIZE} operands
+// returning a result and status
+func Div${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) {
+ q, _, ok := Quotient${SIZE}(a, b)
+ return q, ok
+}
+
+// Div${SIZE}p is the unchecked panicing version of Div${SIZE}
+func Div${SIZE}p(a, b int${SIZE}) int${SIZE} {
+ r, ok := Div${SIZE}(a, b)
+ if !ok {
+ panic(\"division failure\")
+ }
+ return r
+}
+
+// Quotient${SIZE} performs + operation on two int${SIZE} operands
+// returning a quotient, a remainder and status
+func Quotient${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) {
+ if b == 0 {
+ return 0, 0, false
+ }
+ c := a / b
+ status := (c < 0) == ((a < 0) != (b < 0))
+ return c, a % b, status
+}
+"
+done
+
+go run -modfile ../../../misc/devdeps/go.mod mvdan.cc/gofumpt -w overflow_impl.go
diff --git a/tm2/pkg/overflow/overflow_test.go b/tm2/pkg/overflow/overflow_test.go
new file mode 100644
index 00000000000..2b2d345b55d
--- /dev/null
+++ b/tm2/pkg/overflow/overflow_test.go
@@ -0,0 +1,115 @@
+package overflow
+
+import (
+ "fmt"
+ "math"
+ "testing"
+)
+
+// sample all possibilities of 8 bit numbers
+// by checking against 64 bit numbers
+
+func TestAlgorithms(t *testing.T) {
+ errors := 0
+
+ for a64 := int64(math.MinInt8); a64 <= int64(math.MaxInt8); a64++ {
+ for b64 := int64(math.MinInt8); b64 <= int64(math.MaxInt8) && errors < 10; b64++ {
+ a8 := int8(a64)
+ b8 := int8(b64)
+
+ if int64(a8) != a64 || int64(b8) != b64 {
+ t.Fatal("LOGIC FAILURE IN TEST")
+ }
+
+ // ADDITION
+ {
+ r64 := a64 + b64
+
+ // now the verification
+ result, ok := Add8(a8, b8)
+ if ok && int64(result) != r64 {
+ t.Errorf("failed to fail on %v + %v = %v instead of %v\n",
+ a8, b8, result, r64)
+ errors++
+ }
+ if !ok && int64(result) == r64 {
+ t.Fail()
+ errors++
+ }
+ }
+
+ // SUBTRACTION
+ {
+ r64 := a64 - b64
+
+ // now the verification
+ result, ok := Sub8(a8, b8)
+ if ok && int64(result) != r64 {
+ t.Errorf("failed to fail on %v - %v = %v instead of %v\n",
+ a8, b8, result, r64)
+ }
+ if !ok && int64(result) == r64 {
+ t.Fail()
+ errors++
+ }
+ }
+
+ // MULTIPLICATION
+ {
+ r64 := a64 * b64
+
+ // now the verification
+ result, ok := Mul8(a8, b8)
+ if ok && int64(result) != r64 {
+ t.Errorf("failed to fail on %v * %v = %v instead of %v\n",
+ a8, b8, result, r64)
+ errors++
+ }
+ if !ok && int64(result) == r64 {
+ t.Fail()
+ errors++
+ }
+ }
+
+ // DIVISION
+ if b8 != 0 {
+ r64 := a64 / b64
+
+ // now the verification
+ result, _, ok := Quotient8(a8, b8)
+ if ok && int64(result) != r64 {
+ t.Errorf("failed to fail on %v / %v = %v instead of %v\n",
+ a8, b8, result, r64)
+ errors++
+ }
+ if !ok && result != 0 && int64(result) == r64 {
+ t.Fail()
+ errors++
+ }
+ }
+ }
+ }
+}
+
+func TestQuotient(t *testing.T) {
+ q, r, ok := Quotient(100, 3)
+ if r != 1 || q != 33 || !ok {
+ t.Errorf("expected 100/3 => 33, r=1")
+ }
+ if _, _, ok = Quotient(1, 0); ok {
+ t.Error("unexpected lack of failure")
+ }
+}
+
+//func TestAdditionInt(t *testing.T) {
+// fmt.Printf("\nminint8 = %v\n", math.MinInt8)
+// fmt.Printf("maxint8 = %v\n\n", math.MaxInt8)
+// fmt.Printf("maxint32 = %v\n", math.MaxInt32)
+// fmt.Printf("minint32 = %v\n\n", math.MinInt32)
+// fmt.Printf("maxint64 = %v\n", math.MaxInt64)
+// fmt.Printf("minint64 = %v\n\n", math.MinInt64)
+//}
+
+func Test64(t *testing.T) {
+ fmt.Println("64bit:", _is64Bit())
+}
diff --git a/tm2/pkg/std/coin.go b/tm2/pkg/std/coin.go
index 6457b193a6b..fba20a5ba78 100644
--- a/tm2/pkg/std/coin.go
+++ b/tm2/pkg/std/coin.go
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/gnolang/gno/tm2/pkg/errors"
- "github.com/gnolang/overflow"
+ "github.com/gnolang/gno/tm2/pkg/overflow"
)
// -----------------------------------------------------------------------------
diff --git a/tm2/pkg/store/gas/store.go b/tm2/pkg/store/gas/store.go
index db5ea7a79b0..81e898a90d8 100644
--- a/tm2/pkg/store/gas/store.go
+++ b/tm2/pkg/store/gas/store.go
@@ -1,9 +1,9 @@
package gas
import (
+ "github.com/gnolang/gno/tm2/pkg/overflow"
"github.com/gnolang/gno/tm2/pkg/store/types"
"github.com/gnolang/gno/tm2/pkg/store/utils"
- "github.com/gnolang/overflow"
)
var _ types.Store = &Store{}
diff --git a/tm2/pkg/store/types/gas.go b/tm2/pkg/store/types/gas.go
index fd631dd3259..9d1f3d70c28 100644
--- a/tm2/pkg/store/types/gas.go
+++ b/tm2/pkg/store/types/gas.go
@@ -3,7 +3,7 @@ package types
import (
"math"
- "github.com/gnolang/overflow"
+ "github.com/gnolang/gno/tm2/pkg/overflow"
)
// Gas consumption descriptors.
diff --git a/tm2/pkg/store/types/gas_test.go b/tm2/pkg/store/types/gas_test.go
index 410ba0b7e92..115d347bd5e 100644
--- a/tm2/pkg/store/types/gas_test.go
+++ b/tm2/pkg/store/types/gas_test.go
@@ -4,7 +4,7 @@ import (
"math"
"testing"
- "github.com/gnolang/overflow"
+ "github.com/gnolang/gno/tm2/pkg/overflow"
"github.com/stretchr/testify/require"
)