ordinal.go RawHistoryFolder
1// SPDX-License-Identifier: MIT
2
3package expr
4
5import (
6 "sort"
7 "strings"
8)
9
10// The runner compares and looks up strings with .NET's
11// StringComparison.OrdinalIgnoreCase and StringComparer.OrdinalIgnoreCase:
12// every code point is replaced by its simple upper-case form (a per-character
13// table, not full Unicode case mapping), then the UTF-16 code units are
14// compared. Sources, all in actions/runner src/Sdk/DTExpressions2/Expressions2:
15//
16// == != < <= > >= EvaluationResult.cs AbstractEqual/AbstractGreaterThan/
17// AbstractLessThan: String.Equals / String.Compare
18// contains Sdk/Functions/Contains.cs: IndexOf(..., OrdinalIgnoreCase)
19// startsWith Sdk/Functions/StartsWith.cs
20// endsWith Sdk/Functions/EndsWith.cs
21// contexts, functions and dictionary keys:
22// ExpressionParser.cs ParseContext and
23// DTPipelines/Pipelines/ContextData/DictionaryContextData.cs
24// (Dictionary<string,int>(StringComparer.OrdinalIgnoreCase))
25//
26// Differences from the JavaScript reference: "ß" is not "SS" (no multi-
27// character mappings), the Kelvin sign is not "k", the dotless "ı" and the
28// long "ſ" stay themselves, and contains() uses the same folding as the other
29// comparisons. fold_tables.go is generated from the runner's own behaviour
30// (go generate, gen/runner --fold).
31
32//go:generate sh -c "dotnet gen/runner/bin/Release/net9.0/runner.dll --fold | go run gen/fold.go -o fold_tables.go"
33
34// ordinalFoldRune returns the code point that r is folded to.
35func ordinalFoldRune(r rune) rune {
36 if r < 0x80 {
37 if r >= 'a' && r <= 'z' {
38 return r - 'a' + 'A'
39 }
40 return r
41 }
42 i := sort.Search(len(ordinalUpper), func(i int) bool { return ordinalUpper[i][0] >= r })
43 if i < len(ordinalUpper) && ordinalUpper[i][0] == r {
44 return ordinalUpper[i][1]
45 }
46 return r
47}
48
49// ordinalFold folds s. Bytes that are not UTF-8 become U+FFFD, as everywhere
50// in this package.
51func ordinalFold(s string) string {
52 if isASCII(s) {
53 return strings.ToUpper(s)
54 }
55 var b strings.Builder
56 b.Grow(len(s))
57 for i := 0; i < len(s); {
58 r, w := decodeRune(s[i:])
59 i += w
60 writeRune(&b, ordinalFoldRune(r))
61 }
62 return b.String()
63}
64
65// ordinalEqual is String.Equals(a, b, OrdinalIgnoreCase).
66func ordinalEqual(a, b string) bool { return ordinalFold(a) == ordinalFold(b) }
67
68// ordinalCompare is Math.Sign(String.Compare(a, b, OrdinalIgnoreCase)). The
69// runner orders by code point after folding (a supplementary character sorts
70// after U+E000..U+FFFF, unlike in JavaScript, which compares UTF-16 units),
71// which is the byte order of UTF-8.
72func ordinalCompare(a, b string) int { return strings.Compare(ordinalFold(a), ordinalFold(b)) }
73
74// foldKey is the case-insensitive form of a dictionary key, context name or
75// function name.
76func foldKey(key string) string { return ordinalFold(key) }