sievedemo.gno
3.14 Kb · 127 lines
1// Package sievedemo is a small gnoweb demo of the Sieve of Eratosthenes
2// provided by the [p/moul/x/daily/sieve](/p/moul/x/daily/sieve/v0) library: it
3// renders the primes up to MaxN, the first 100 primes, and an interactive
4// "primes up to n" grid.
5//
6// It contains no sieve logic of its own — everything comes from
7// `sieve.PrimesUpTo` and `sieve.MaxN`.
8package sievedemo
9
10import (
11 "strconv"
12 "strings"
13
14 "gno.land/p/moul/x/daily/sieve/v0"
15)
16
17// Render renders the sieve for gnoweb.
18//
19// Render("") / Render("/") -> first 100 primes + total count up to MaxN
20// Render("/<n>") -> every prime <= n, laid out as a grid
21func Render(path string) string {
22 n, hasN := parseN(path)
23
24 var b strings.Builder
25 b.WriteString("# Sieve of Eratosthenes\n\n")
26 b.WriteString("A deterministic on-chain port of Go's classic concurrent prime-sieve example, ")
27 b.WriteString("demoing the [`p/moul/x/daily/sieve`](/p/moul/x/daily/sieve/v0) library.\n\n")
28
29 if !hasN {
30 primes := sieve.PrimesUpTo(sieve.MaxN)
31 b.WriteString("There are **")
32 b.WriteString(strconv.Itoa(len(primes)))
33 b.WriteString("** primes up to ")
34 b.WriteString(strconv.Itoa(sieve.MaxN))
35 b.WriteString(".\n\n")
36 b.WriteString("## First 100 primes\n\n")
37 first := primes
38 if len(first) > 100 {
39 first = first[:100]
40 }
41 b.WriteString(grid(first, 10))
42 b.WriteString("\n> Try `/500` or `/1000` to sieve up to any n (max ")
43 b.WriteString(strconv.Itoa(sieve.MaxN))
44 b.WriteString(").\n")
45 return b.String()
46 }
47
48 if n < 2 {
49 b.WriteString("No primes <= ")
50 b.WriteString(strconv.Itoa(n))
51 b.WriteString(".\n")
52 return b.String()
53 }
54
55 clamped := n
56 if clamped > sieve.MaxN {
57 clamped = sieve.MaxN
58 }
59 primes := sieve.PrimesUpTo(clamped)
60 b.WriteString("## Primes up to ")
61 b.WriteString(strconv.Itoa(clamped))
62 if clamped != n {
63 b.WriteString(" (clamped from ")
64 b.WriteString(strconv.Itoa(n))
65 b.WriteString(")")
66 }
67 b.WriteString("\n\n")
68 b.WriteString("Count: **")
69 b.WriteString(strconv.Itoa(len(primes)))
70 b.WriteString("**\n\n")
71 b.WriteString(grid(primes, 10))
72 return b.String()
73}
74
75// parseN extracts n from a Render path like "/1000". Returns hasN=false for
76// the empty/root path.
77func parseN(path string) (int, bool) {
78 s := strings.TrimSpace(path)
79 s = strings.TrimPrefix(s, "/")
80 if s == "" {
81 return 0, false
82 }
83 // keep only the first segment
84 if i := strings.IndexByte(s, '/'); i >= 0 {
85 s = s[:i]
86 }
87 v, err := strconv.Atoi(s)
88 if err != nil {
89 return 0, false
90 }
91 return v, true
92}
93
94// grid formats primes into a Markdown table with `cols` columns per row.
95func grid(primes []int, cols int) string {
96 if len(primes) == 0 {
97 return "_none_\n"
98 }
99 if cols < 1 {
100 cols = 1
101 }
102
103 var b strings.Builder
104 // header + separator so gnoweb renders it as a table
105 b.WriteString("|")
106 for c := 0; c < cols; c++ {
107 b.WriteString(" · |")
108 }
109 b.WriteString("\n|")
110 for c := 0; c < cols; c++ {
111 b.WriteString("---|")
112 }
113 b.WriteString("\n")
114
115 for i := 0; i < len(primes); i += cols {
116 b.WriteString("|")
117 for c := 0; c < cols; c++ {
118 b.WriteString(" ")
119 if i+c < len(primes) {
120 b.WriteString(strconv.Itoa(primes[i+c]))
121 }
122 b.WriteString(" |")
123 }
124 b.WriteString("\n")
125 }
126 return b.String()
127}