1// SPDX-License-Identifier: MIT
2
3//go:build ignore
4
5// Command casing reads the JSON produced by casing_dump.mjs on stdin and
6// writes casing_tables.go (to the file given with -o, else on stdout). Only the code points whose JavaScript
7// mapping differs from the Go simple mapping (unicode.ToUpper/ToLower) are
8// emitted: multi-character expansions (U+00DF to "SS") and Unicode version
9// differences between Go and the JavaScript engine.
10package main
11
12import (
13 "encoding/json"
14 "flag"
15 "fmt"
16 "go/format"
17 "io"
18 "os"
19 "sort"
20 "strconv"
21 "unicode"
22)
23
24type dump struct {
25 Upper map[string]string `json:"upper"`
26 Lower map[string]string `json:"lower"`
27}
28
29func main() {
30 out := flag.String("o", "", "output file (default stdout)")
31 flag.Parse()
32 var d dump
33 if err := json.NewDecoder(os.Stdin).Decode(&d); err != nil {
34 fmt.Fprintln(os.Stderr, err)
35 os.Exit(1)
36 }
37 // Everything is printed to a pipe, gofmt-ed and then written, so that the
38 // generated file is already in canonical form.
39 r, w, err := os.Pipe()
40 if err != nil {
41 fatal(err)
42 }
43 stdout := os.Stdout
44 os.Stdout = w
45 done := make(chan []byte)
46 go func() {
47 b, _ := io.ReadAll(r)
48 done <- b
49 }()
50 defer func() {
51 _ = w.Close()
52 os.Stdout = stdout
53 src, err := format.Source(<-done)
54 if err != nil {
55 fatal(err)
56 }
57 if *out == "" {
58 _, _ = stdout.Write(src)
59 return
60 }
61 if err := os.WriteFile(*out, src, 0o644); err != nil {
62 fatal(err)
63 }
64 }()
65 fmt.Println("// SPDX-License-Identifier: MIT")
66 fmt.Println()
67 fmt.Println("// Code generated by gen/casing.go from gen/casing_dump.mjs; DO NOT EDIT.")
68 fmt.Println()
69 fmt.Println("package expr")
70 fmt.Println()
71 fmt.Println("// casingGoUnicodeVersion is the Unicode version of the Go toolchain that generated")
72 fmt.Println("// the tables below: they are relative to its unicode.ToUpper and unicode.ToLower.")
73 fmt.Printf("const casingGoUnicodeVersion = %q\n", unicode.Version)
74 fmt.Println()
75 emit("jsUpperDelta", "JavaScript String.prototype.toUpperCase", d.Upper, unicode.ToUpper)
76 emit("jsLowerDelta", "JavaScript String.prototype.toLowerCase (without the Final_Sigma context)", d.Lower, unicode.ToLower)
77}
78
79func emit(name, doc string, m map[string]string, goMap func(rune) rune) {
80 // Include code points that JavaScript maps to themselves but Go does not.
81 type entry struct {
82 cp rune
83 s string
84 }
85 var out []entry
86 seen := map[rune]bool{}
87 for k, v := range m {
88 n, _ := strconv.Atoi(k)
89 r := rune(n)
90 seen[r] = true
91 if string(goMap(r)) != v {
92 out = append(out, entry{r, v})
93 }
94 }
95 for r := rune(0); r <= unicode.MaxRune; r++ {
96 if r >= 0xd800 && r <= 0xdfff || seen[r] {
97 continue
98 }
99 if goMap(r) != r {
100 out = append(out, entry{r, string(r)})
101 }
102 }
103 sort.Slice(out, func(i, j int) bool { return out[i].cp < out[j].cp })
104 fmt.Printf("// %s maps the code points whose %s result differs from the Go simple mapping.\n", name, doc)
105 fmt.Printf("var %s = map[rune]string{\n", name)
106 for _, e := range out {
107 fmt.Printf("\t0x%04X: %s, // %s\n", e.cp, strconv.Quote(e.s), strconv.QuoteToGraphic(string(e.cp)))
108 }
109 fmt.Println("}")
110 fmt.Println()
111}
112
113func fatal(err error) {
114 fmt.Fprintln(os.Stderr, err)
115 os.Exit(1)
116}