You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
6.0 KiB
187 lines
6.0 KiB
//go:build ignore
|
|
|
|
// Writes lib/src/go_unicode.dart: the three sets of code points that Go's
|
|
// strings.ToLower, strings.ToUpper and strings.TrimSpace depend on, as the
|
|
// package unicode of the Go that runs it defines them, for the key strings
|
|
// of authorkey (stage 7b of docs/PLAN_dart.md):
|
|
//
|
|
// - the code points r with unicode.ToLower(r) != r, which strings.ToLower
|
|
// changes, so that ParsePublic tells "written in lower case" as Go does;
|
|
// - those with unicode.ToUpper(r) != r, for ParseSecret;
|
|
// - those with unicode.IsSpace(r), which strings.TrimSpace trims from a
|
|
// line of a key file.
|
|
//
|
|
// Each set is written as runs [first, last, stride], stride 1 or 2, in
|
|
// increasing order. No value is written by hand: the generator scans every
|
|
// code point and checks the runs it writes against the functions of Go.
|
|
//
|
|
// Run it with the toolchain of datekeys-go, Go 1.26.8, Unicode 15.0.0,
|
|
// without changing anything there:
|
|
//
|
|
// cd ../datekeys-go && go run ../datekeys-dart/tool/go_unicode_tables.go \
|
|
// -out ../datekeys-dart/lib/src/go_unicode.dart
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// runs returns the code points of in as runs [first, last, stride]: a run
|
|
// of stride 2 is taken when it covers at least three points, so that
|
|
// alternating cases stay short.
|
|
func runs(in func(rune) bool) [][3]rune {
|
|
var pts []rune
|
|
for r := rune(0); r <= unicode.MaxRune; r++ {
|
|
if r >= 0xd800 && r <= 0xdfff {
|
|
continue // not in valid UTF-8: a decoder gives U+FFFD instead
|
|
}
|
|
if in(r) {
|
|
pts = append(pts, r)
|
|
}
|
|
}
|
|
var out [][3]rune
|
|
for i := 0; i < len(pts); {
|
|
j := i
|
|
for j+1 < len(pts) && pts[j+1] == pts[j]+1 {
|
|
j++
|
|
}
|
|
k := i
|
|
for k+1 < len(pts) && pts[k+1] == pts[k]+2 {
|
|
k++
|
|
}
|
|
switch {
|
|
case j > i:
|
|
out = append(out, [3]rune{pts[i], pts[j], 1})
|
|
i = j + 1
|
|
case k-i >= 2:
|
|
out = append(out, [3]rune{pts[i], pts[k], 2})
|
|
i = k + 1
|
|
default:
|
|
out = append(out, [3]rune{pts[i], pts[i], 1})
|
|
i++
|
|
}
|
|
}
|
|
// Check the runs against the function.
|
|
member := map[rune]bool{}
|
|
for _, r := range out {
|
|
for c := r[0]; c <= r[1]; c += r[2] {
|
|
if member[c] {
|
|
log.Fatalf("U+%04X twice", c)
|
|
}
|
|
member[c] = true
|
|
}
|
|
}
|
|
for r := rune(0); r <= unicode.MaxRune; r++ {
|
|
if r >= 0xd800 && r <= 0xdfff {
|
|
continue
|
|
}
|
|
if member[r] != in(r) {
|
|
log.Fatalf("U+%04X: the runs disagree", r)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func list(b *strings.Builder, name, doc string, rs [][3]rune) {
|
|
fmt.Fprintf(b, "// %s\nconst List<int> %s = [\n", doc, name)
|
|
for _, r := range rs {
|
|
fmt.Fprintf(b, " 0x%04x, 0x%04x, %d,\n", r[0], r[1], r[2])
|
|
}
|
|
b.WriteString("];\n\n")
|
|
}
|
|
|
|
func main() {
|
|
out := flag.String("out", "", "the Dart file to write")
|
|
flag.Parse()
|
|
if *out == "" {
|
|
log.Fatal("usage: go run tool/go_unicode_tables.go -out lib/src/go_unicode.dart")
|
|
}
|
|
// strings.ToLower and ToUpper change a string exactly when one of its
|
|
// runes changes, or when it is not valid UTF-8: check that premise on
|
|
// every code point.
|
|
for r := rune(0); r <= unicode.MaxRune; r++ {
|
|
if r >= 0xd800 && r <= 0xdfff {
|
|
continue
|
|
}
|
|
s := string(r)
|
|
if (strings.ToLower(s) != s) != (unicode.ToLower(r) != r) {
|
|
log.Fatalf("strings.ToLower and unicode.ToLower disagree on U+%04X", r)
|
|
}
|
|
if (strings.ToUpper(s) != s) != (unicode.ToUpper(r) != r) {
|
|
log.Fatalf("strings.ToUpper and unicode.ToUpper disagree on U+%04X", r)
|
|
}
|
|
if (strings.TrimSpace(s) == "") != unicode.IsSpace(r) {
|
|
log.Fatalf("strings.TrimSpace and unicode.IsSpace disagree on U+%04X", r)
|
|
}
|
|
}
|
|
if strings.ToLower("\xff") == "\xff" || strings.ToUpper("\xff") == "\xff" {
|
|
log.Fatal("an invalid byte no longer changes")
|
|
}
|
|
if strings.TrimSpace(" \xff ") != "\xff" || utf8.RuneError != 0xfffd {
|
|
log.Fatal("TrimSpace no longer stops at an invalid byte")
|
|
}
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, `// Code generated by tool/go_unicode_tables.go with Go %s, Unicode %s.
|
|
// DO NOT EDIT: regenerate it.
|
|
//
|
|
// The lists run to hundreds of lines: the formatter leaves them as the
|
|
// generator writes them.
|
|
// dart format off
|
|
|
|
/// The code points that Go's strings.ToLower, strings.ToUpper and
|
|
/// strings.TrimSpace depend on, as the package unicode of Go %s defines
|
|
/// them, Unicode %s: authorkey.dart reads the strings of a key and the
|
|
/// lines of a key file with them, so that its errors are those of Go
|
|
/// whatever the bytes. They are not the case mapping of the platform, nor
|
|
/// the tables of the path rules (Unicode 18.0.0, spec §29.5.1).
|
|
///
|
|
/// Internal: lib/datekeys.dart does not export it.
|
|
library;
|
|
|
|
/// The version of Unicode of the package unicode of Go %s.
|
|
const goUnicodeVersion = '%s';
|
|
|
|
`, runtime.Version(), unicode.Version, runtime.Version(), unicode.Version, runtime.Version(), unicode.Version)
|
|
list(&b, "_lowerChanges", "The runs [first, last, stride] of r with unicode.ToLower(r) != r.", runs(func(r rune) bool { return unicode.ToLower(r) != r }))
|
|
list(&b, "_upperChanges", "The runs [first, last, stride] of r with unicode.ToUpper(r) != r.", runs(func(r rune) bool { return unicode.ToUpper(r) != r }))
|
|
list(&b, "_spaces", "The runs [first, last, stride] of r with unicode.IsSpace(r).", runs(unicode.IsSpace))
|
|
b.WriteString(`// Whether r is in the runs of table, by binary search on the first points.
|
|
bool _inRuns(List<int> table, int r) {
|
|
var lo = 0;
|
|
var hi = table.length ~/ 3;
|
|
while (lo < hi) {
|
|
final m = (lo + hi) ~/ 2;
|
|
if (table[3 * m + 1] < r) {
|
|
lo = m + 1;
|
|
} else {
|
|
hi = m;
|
|
}
|
|
}
|
|
if (lo == table.length ~/ 3) return false;
|
|
final first = table[3 * lo];
|
|
return r >= first && (r - first) % table[3 * lo + 2] == 0;
|
|
}
|
|
|
|
/// Whether Go's unicode.ToLower changes the code point [r].
|
|
bool goLowerChanges(int r) => _inRuns(_lowerChanges, r);
|
|
|
|
/// Whether Go's unicode.ToUpper changes the code point [r].
|
|
bool goUpperChanges(int r) => _inRuns(_upperChanges, r);
|
|
|
|
/// Whether [r] is a space for Go's unicode.IsSpace.
|
|
bool goIsSpace(int r) => _inRuns(_spaces, r);
|
|
`)
|
|
if err := os.WriteFile(*out, []byte(b.String()), 0o644); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Printf("wrote %s\n", *out)
|
|
}
|