iam-floyd 0.848.0 → 0.849.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.markdownlint-cli2.yaml +6 -2
- package/.prettierignore +1 -0
- package/AGENTS.md +39 -21
- package/README.md +166 -43
- package/dotnet/src/IAM.Floyd/IAM.Floyd.csproj +36 -0
- package/dotnet/src/IAM.Floyd/Js.cs +272 -0
- package/dotnet/src/IAM.Floyd/Json.cs +151 -0
- package/eslint.config.mjs +1 -0
- package/go/iamfloyd/go.mod +3 -0
- package/go/iamfloyd/helpers.go +25 -0
- package/go/iamfloyd/internal/js/collections.go +219 -0
- package/go/iamfloyd/internal/js/json.go +95 -0
- package/go/iamfloyd/internal/js/strings.go +150 -0
- package/go/iamfloyd/internal/js/values.go +251 -0
- package/java/pom.xml +140 -0
- package/java/src/main/java/com/udondan/iamFloyd/Js.java +210 -0
- package/java/src/main/java/com/udondan/iamFloyd/Json.java +137 -0
- package/lib/shared/compact.js +1 -1
- package/lib/shared/policy-statement/2-conditions.d.ts +1 -1
- package/lint/checkstyle-suppressions.xml +7 -0
- package/lint/pom.xml +2 -0
- package/package.json +2 -2
- package/ruff.toml +2 -2
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Globalization;
|
|
5
|
+
using System.Text;
|
|
6
|
+
using System.Text.RegularExpressions;
|
|
7
|
+
|
|
8
|
+
namespace IAM.Floyd;
|
|
9
|
+
|
|
10
|
+
/// <summary>
|
|
11
|
+
/// The semantics of JavaScript that the transpiled code relies on.
|
|
12
|
+
/// </summary>
|
|
13
|
+
internal static class Js
|
|
14
|
+
{
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Whether a value is a number.
|
|
17
|
+
/// </summary>
|
|
18
|
+
public static bool IsNumber(object value)
|
|
19
|
+
{
|
|
20
|
+
return value is double || value is int || value is long || value is float
|
|
21
|
+
|| value is short || value is byte || value is decimal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// <summary>
|
|
25
|
+
/// Array.isArray: whether a value is a sequence, other than a string or a dictionary.
|
|
26
|
+
/// </summary>
|
|
27
|
+
public static bool IsArray(object value)
|
|
28
|
+
{
|
|
29
|
+
return value is IEnumerable && !(value is string) && !(value is IDictionary);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// <summary>
|
|
33
|
+
/// A sequence as list.
|
|
34
|
+
/// </summary>
|
|
35
|
+
public static List<object> ToList(object value)
|
|
36
|
+
{
|
|
37
|
+
var result = new List<object>();
|
|
38
|
+
foreach (var item in (IEnumerable)value)
|
|
39
|
+
{
|
|
40
|
+
result.Add(item);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// <summary>
|
|
46
|
+
/// The result of the typeof operator of JavaScript.
|
|
47
|
+
/// </summary>
|
|
48
|
+
public static string Typeof(object value)
|
|
49
|
+
{
|
|
50
|
+
if (value == null)
|
|
51
|
+
{
|
|
52
|
+
return "undefined";
|
|
53
|
+
}
|
|
54
|
+
if (value is bool)
|
|
55
|
+
{
|
|
56
|
+
return "boolean";
|
|
57
|
+
}
|
|
58
|
+
if (IsNumber(value))
|
|
59
|
+
{
|
|
60
|
+
return "number";
|
|
61
|
+
}
|
|
62
|
+
if (value is string)
|
|
63
|
+
{
|
|
64
|
+
return "string";
|
|
65
|
+
}
|
|
66
|
+
return "object";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// <summary>
|
|
70
|
+
/// A value as JavaScript converts it to a string.
|
|
71
|
+
/// </summary>
|
|
72
|
+
public static string ToString(object value)
|
|
73
|
+
{
|
|
74
|
+
switch (value)
|
|
75
|
+
{
|
|
76
|
+
case null:
|
|
77
|
+
return "undefined";
|
|
78
|
+
case string text:
|
|
79
|
+
return text;
|
|
80
|
+
case bool flag:
|
|
81
|
+
return flag ? "true" : "false";
|
|
82
|
+
case double number:
|
|
83
|
+
return Number(number);
|
|
84
|
+
case float number:
|
|
85
|
+
return Number(number);
|
|
86
|
+
case decimal number:
|
|
87
|
+
return Number((double)number);
|
|
88
|
+
case IFormattable formattable when IsNumber(value):
|
|
89
|
+
return formattable.ToString(null, CultureInfo.InvariantCulture);
|
|
90
|
+
case IEnumerable items when !(value is IDictionary):
|
|
91
|
+
var result = new StringBuilder();
|
|
92
|
+
var first = true;
|
|
93
|
+
foreach (var item in items)
|
|
94
|
+
{
|
|
95
|
+
if (!first)
|
|
96
|
+
{
|
|
97
|
+
result.Append(',');
|
|
98
|
+
}
|
|
99
|
+
first = false;
|
|
100
|
+
if (item != null)
|
|
101
|
+
{
|
|
102
|
+
result.Append(ToString(item));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result.ToString();
|
|
106
|
+
}
|
|
107
|
+
return value.ToString();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// <summary>
|
|
111
|
+
/// A number as JavaScript formats it: the shortest digits that round-trip.
|
|
112
|
+
/// </summary>
|
|
113
|
+
public static string Number(double value)
|
|
114
|
+
{
|
|
115
|
+
if (double.IsNaN(value))
|
|
116
|
+
{
|
|
117
|
+
return "NaN";
|
|
118
|
+
}
|
|
119
|
+
if (double.IsInfinity(value))
|
|
120
|
+
{
|
|
121
|
+
return value > 0 ? "Infinity" : "-Infinity";
|
|
122
|
+
}
|
|
123
|
+
if (value == 0)
|
|
124
|
+
{
|
|
125
|
+
return "0";
|
|
126
|
+
}
|
|
127
|
+
if (value < 0)
|
|
128
|
+
{
|
|
129
|
+
return "-" + Number(-value);
|
|
130
|
+
}
|
|
131
|
+
// the shortest representation that parses back to the same value, e.g. 1.5E-07
|
|
132
|
+
var shortest = value.ToString("R", CultureInfo.InvariantCulture);
|
|
133
|
+
var e = shortest.IndexOf('E');
|
|
134
|
+
var mantissa = e < 0 ? shortest : shortest.Substring(0, e);
|
|
135
|
+
var exponent = e < 0 ? 0 : int.Parse(shortest.Substring(e + 1), CultureInfo.InvariantCulture);
|
|
136
|
+
var point = mantissa.IndexOf('.');
|
|
137
|
+
var digits = point < 0 ? mantissa : mantissa.Remove(point, 1);
|
|
138
|
+
// n is the position of the decimal point relative to the first digit
|
|
139
|
+
var n = (point < 0 ? mantissa.Length : point) + exponent;
|
|
140
|
+
while (digits.Length > 1 && digits[0] == '0')
|
|
141
|
+
{
|
|
142
|
+
digits = digits.Substring(1);
|
|
143
|
+
n--;
|
|
144
|
+
}
|
|
145
|
+
// trailing zeros are not significant
|
|
146
|
+
while (digits.Length > 1 && digits[digits.Length - 1] == '0')
|
|
147
|
+
{
|
|
148
|
+
digits = digits.Substring(0, digits.Length - 1);
|
|
149
|
+
}
|
|
150
|
+
var k = digits.Length;
|
|
151
|
+
var result = new StringBuilder();
|
|
152
|
+
if (k <= n && n <= 21)
|
|
153
|
+
{
|
|
154
|
+
result.Append(digits).Append('0', n - k);
|
|
155
|
+
}
|
|
156
|
+
else if (0 < n && n <= 21)
|
|
157
|
+
{
|
|
158
|
+
result.Append(digits, 0, n).Append('.').Append(digits.Substring(n));
|
|
159
|
+
}
|
|
160
|
+
else if (-6 < n && n <= 0)
|
|
161
|
+
{
|
|
162
|
+
result.Append("0.").Append('0', -n).Append(digits);
|
|
163
|
+
}
|
|
164
|
+
else
|
|
165
|
+
{
|
|
166
|
+
result.Append(digits[0]);
|
|
167
|
+
if (k > 1)
|
|
168
|
+
{
|
|
169
|
+
result.Append('.').Append(digits.Substring(1));
|
|
170
|
+
}
|
|
171
|
+
result.Append('e').Append(n - 1 >= 0 ? "+" : "-")
|
|
172
|
+
.Append(Math.Abs(n - 1).ToString(CultureInfo.InvariantCulture));
|
|
173
|
+
}
|
|
174
|
+
return result.ToString();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// <summary>
|
|
178
|
+
/// String.prototype.substring: the indexes are clamped, and swapped if start is after end.
|
|
179
|
+
/// </summary>
|
|
180
|
+
public static string Substring(string value, int start)
|
|
181
|
+
{
|
|
182
|
+
return Substring(value, start, value.Length);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// <summary>
|
|
186
|
+
/// String.prototype.substring: the indexes are clamped, and swapped if start is after end.
|
|
187
|
+
/// </summary>
|
|
188
|
+
public static string Substring(string value, int start, int end)
|
|
189
|
+
{
|
|
190
|
+
start = Math.Max(0, Math.Min(start, value.Length));
|
|
191
|
+
end = Math.Max(0, Math.Min(end, value.Length));
|
|
192
|
+
return start <= end
|
|
193
|
+
? value.Substring(start, end - start)
|
|
194
|
+
: value.Substring(end, start - end);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// <summary>
|
|
198
|
+
/// Array.prototype.filter.
|
|
199
|
+
/// </summary>
|
|
200
|
+
public static List<T> Filter<T>(List<T> list, Func<T, int, bool> predicate)
|
|
201
|
+
{
|
|
202
|
+
var result = new List<T>();
|
|
203
|
+
for (var i = 0; i < list.Count; i++)
|
|
204
|
+
{
|
|
205
|
+
if (predicate(list[i], i))
|
|
206
|
+
{
|
|
207
|
+
result.Add(list[i]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// <summary>
|
|
214
|
+
/// Array.prototype.map.
|
|
215
|
+
/// </summary>
|
|
216
|
+
public static List<TResult> Map<T, TResult>(List<T> list, Func<T, int, TResult> fn)
|
|
217
|
+
{
|
|
218
|
+
var result = new List<TResult>();
|
|
219
|
+
for (var i = 0; i < list.Count; i++)
|
|
220
|
+
{
|
|
221
|
+
result.Add(fn(list[i], i));
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/// <summary>
|
|
227
|
+
/// Array.prototype.sort without compare function: sorts strings by UTF-16 code units.
|
|
228
|
+
/// </summary>
|
|
229
|
+
public static List<string> Sort(List<string> list)
|
|
230
|
+
{
|
|
231
|
+
list.Sort(string.CompareOrdinal);
|
|
232
|
+
return list;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// <summary>
|
|
236
|
+
/// Date.prototype.toISOString. Dates of an unspecified kind are in UTC.
|
|
237
|
+
/// </summary>
|
|
238
|
+
public static string ToISOString(DateTime value)
|
|
239
|
+
{
|
|
240
|
+
if (value.Kind == DateTimeKind.Local)
|
|
241
|
+
{
|
|
242
|
+
value = value.ToUniversalTime();
|
|
243
|
+
}
|
|
244
|
+
return value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// <summary>
|
|
248
|
+
/// A regular expression like new RegExp(pattern, flags), with the flags i, m and s.
|
|
249
|
+
/// </summary>
|
|
250
|
+
public static Regex RegExp(string pattern, string flags)
|
|
251
|
+
{
|
|
252
|
+
var options = RegexOptions.None;
|
|
253
|
+
foreach (var flag in flags)
|
|
254
|
+
{
|
|
255
|
+
switch (flag)
|
|
256
|
+
{
|
|
257
|
+
case 'i':
|
|
258
|
+
options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
|
|
259
|
+
break;
|
|
260
|
+
case 'm':
|
|
261
|
+
options |= RegexOptions.Multiline;
|
|
262
|
+
break;
|
|
263
|
+
case 's':
|
|
264
|
+
options |= RegexOptions.Singleline;
|
|
265
|
+
break;
|
|
266
|
+
default:
|
|
267
|
+
throw new ArgumentException("Unsupported regular expression flag: " + flag);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return new Regex(pattern, options);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections;
|
|
3
|
+
using System.Globalization;
|
|
4
|
+
using System.Reflection;
|
|
5
|
+
using System.Text;
|
|
6
|
+
|
|
7
|
+
namespace IAM.Floyd;
|
|
8
|
+
|
|
9
|
+
/// <summary>
|
|
10
|
+
/// Serializes statements and their parts to JSON, as <c>JSON.stringify</c> of JavaScript.
|
|
11
|
+
/// </summary>
|
|
12
|
+
public static class Json
|
|
13
|
+
{
|
|
14
|
+
/// <summary>
|
|
15
|
+
/// The JSON of a value: dictionaries in the order of their keys, sequences, strings, numbers,
|
|
16
|
+
/// booleans, null, dates as ISO strings, and objects with a public <c>ToJSON()</c> method, such
|
|
17
|
+
/// as statements.
|
|
18
|
+
/// </summary>
|
|
19
|
+
/// <param name="value">The value</param>
|
|
20
|
+
/// <returns>The JSON, without whitespace</returns>
|
|
21
|
+
public static string Stringify(object value)
|
|
22
|
+
{
|
|
23
|
+
var result = new StringBuilder();
|
|
24
|
+
Write(result, value);
|
|
25
|
+
return result.ToString();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static void Write(StringBuilder result, object value)
|
|
29
|
+
{
|
|
30
|
+
switch (value)
|
|
31
|
+
{
|
|
32
|
+
case null:
|
|
33
|
+
result.Append("null");
|
|
34
|
+
break;
|
|
35
|
+
case string text:
|
|
36
|
+
WriteString(result, text);
|
|
37
|
+
break;
|
|
38
|
+
case bool flag:
|
|
39
|
+
result.Append(flag ? "true" : "false");
|
|
40
|
+
break;
|
|
41
|
+
case double or float or decimal:
|
|
42
|
+
var number = Convert.ToDouble(value, CultureInfo.InvariantCulture);
|
|
43
|
+
result.Append(double.IsNaN(number) || double.IsInfinity(number) ? "null" : Js.Number(number));
|
|
44
|
+
break;
|
|
45
|
+
case IFormattable formattable when Js.IsNumber(value):
|
|
46
|
+
result.Append(formattable.ToString(null, CultureInfo.InvariantCulture));
|
|
47
|
+
break;
|
|
48
|
+
case DateTime date:
|
|
49
|
+
WriteString(result, Js.ToISOString(date));
|
|
50
|
+
break;
|
|
51
|
+
case IDictionary dictionary:
|
|
52
|
+
result.Append('{');
|
|
53
|
+
var first = true;
|
|
54
|
+
foreach (DictionaryEntry entry in dictionary)
|
|
55
|
+
{
|
|
56
|
+
if (!first)
|
|
57
|
+
{
|
|
58
|
+
result.Append(',');
|
|
59
|
+
}
|
|
60
|
+
first = false;
|
|
61
|
+
WriteString(result, Convert.ToString(entry.Key, CultureInfo.InvariantCulture));
|
|
62
|
+
result.Append(':');
|
|
63
|
+
Write(result, entry.Value);
|
|
64
|
+
}
|
|
65
|
+
result.Append('}');
|
|
66
|
+
break;
|
|
67
|
+
case IEnumerable items:
|
|
68
|
+
result.Append('[');
|
|
69
|
+
var firstItem = true;
|
|
70
|
+
foreach (var item in items)
|
|
71
|
+
{
|
|
72
|
+
if (!firstItem)
|
|
73
|
+
{
|
|
74
|
+
result.Append(',');
|
|
75
|
+
}
|
|
76
|
+
firstItem = false;
|
|
77
|
+
Write(result, item);
|
|
78
|
+
}
|
|
79
|
+
result.Append(']');
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
Write(result, ToJson(value));
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static object ToJson(object value)
|
|
88
|
+
{
|
|
89
|
+
var method = value.GetType().GetMethod("ToJSON", Type.EmptyTypes);
|
|
90
|
+
if (method == null)
|
|
91
|
+
{
|
|
92
|
+
throw new ArgumentException("Cannot serialize " + value.GetType().FullName + " to JSON");
|
|
93
|
+
}
|
|
94
|
+
try
|
|
95
|
+
{
|
|
96
|
+
return method.Invoke(value, null);
|
|
97
|
+
}
|
|
98
|
+
catch (TargetInvocationException e) when (e.InnerException != null)
|
|
99
|
+
{
|
|
100
|
+
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
|
|
101
|
+
throw;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private static void WriteString(StringBuilder result, string value)
|
|
106
|
+
{
|
|
107
|
+
result.Append('"');
|
|
108
|
+
for (var i = 0; i < value.Length; i++)
|
|
109
|
+
{
|
|
110
|
+
var c = value[i];
|
|
111
|
+
switch (c)
|
|
112
|
+
{
|
|
113
|
+
case '"':
|
|
114
|
+
result.Append("\\\"");
|
|
115
|
+
break;
|
|
116
|
+
case '\\':
|
|
117
|
+
result.Append("\\\\");
|
|
118
|
+
break;
|
|
119
|
+
case '\b':
|
|
120
|
+
result.Append("\\b");
|
|
121
|
+
break;
|
|
122
|
+
case '\f':
|
|
123
|
+
result.Append("\\f");
|
|
124
|
+
break;
|
|
125
|
+
case '\n':
|
|
126
|
+
result.Append("\\n");
|
|
127
|
+
break;
|
|
128
|
+
case '\r':
|
|
129
|
+
result.Append("\\r");
|
|
130
|
+
break;
|
|
131
|
+
case '\t':
|
|
132
|
+
result.Append("\\t");
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
var loneSurrogate = char.IsHighSurrogate(c)
|
|
136
|
+
? i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1])
|
|
137
|
+
: char.IsLowSurrogate(c) && (i == 0 || !char.IsHighSurrogate(value[i - 1]));
|
|
138
|
+
if (c < 0x20 || loneSurrogate)
|
|
139
|
+
{
|
|
140
|
+
result.Append("\\u").Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
{
|
|
144
|
+
result.Append(c);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
result.Append('"');
|
|
150
|
+
}
|
|
151
|
+
}
|
package/eslint.config.mjs
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package iamfloyd
|
|
2
|
+
|
|
3
|
+
// String returns a pointer to a string, for the parameters of the API
|
|
4
|
+
func String(value string) *string {
|
|
5
|
+
return &value
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Strings returns a pointer to a list of pointers to strings, for the parameters of the API
|
|
9
|
+
func Strings(values ...string) *[]*string {
|
|
10
|
+
list := make([]*string, len(values))
|
|
11
|
+
for i := range values {
|
|
12
|
+
list[i] = &values[i]
|
|
13
|
+
}
|
|
14
|
+
return &list
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Number returns a pointer to a number, for the parameters of the API
|
|
18
|
+
func Number(value float64) *float64 {
|
|
19
|
+
return &value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Bool returns a pointer to a boolean, for the parameters of the API
|
|
23
|
+
func Bool(value bool) *bool {
|
|
24
|
+
return &value
|
|
25
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
package js
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"sort"
|
|
5
|
+
"strconv"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// Entry is an entry of a Record or a Map
|
|
9
|
+
type Entry[K comparable, V any] struct {
|
|
10
|
+
Key K
|
|
11
|
+
Value V
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Record is an object of JavaScript used as dictionary: the keys that are array indexes come
|
|
15
|
+
// first in ascending order, then the other keys in the order they were added.
|
|
16
|
+
type Record[V any] struct {
|
|
17
|
+
keys []string
|
|
18
|
+
values map[string]V
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// NewRecord returns an empty record
|
|
22
|
+
func NewRecord[V any]() *Record[V] {
|
|
23
|
+
return &Record[V]{values: map[string]V{}}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// RecordOf returns a record with the given entries
|
|
27
|
+
func RecordOf[V any](entries ...Entry[string, V]) *Record[V] {
|
|
28
|
+
record := NewRecord[V]()
|
|
29
|
+
for _, entry := range entries {
|
|
30
|
+
record.Set(entry.Key, entry.Value)
|
|
31
|
+
}
|
|
32
|
+
return record
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Get returns the value of a key, or the zero value if the key is missing
|
|
36
|
+
func (r *Record[V]) Get(key string) V {
|
|
37
|
+
return r.values[key]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// GetOr returns the value of a key, or the fallback if the key is missing
|
|
41
|
+
func (r *Record[V]) GetOr(key string, fallback V) V {
|
|
42
|
+
if value, ok := r.values[key]; ok {
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
return fallback
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Set adds or replaces the value of a key
|
|
49
|
+
func (r *Record[V]) Set(key string, value V) {
|
|
50
|
+
if _, ok := r.values[key]; !ok {
|
|
51
|
+
r.keys = append(r.keys, key)
|
|
52
|
+
}
|
|
53
|
+
r.values[key] = value
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Has returns whether the record has a key
|
|
57
|
+
func (r *Record[V]) Has(key string) bool {
|
|
58
|
+
_, ok := r.values[key]
|
|
59
|
+
return ok
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Len returns the number of keys
|
|
63
|
+
func (r *Record[V]) Len() int {
|
|
64
|
+
return len(r.keys)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func arrayIndex(key string) (uint64, bool) {
|
|
68
|
+
if key == "" || (len(key) > 1 && key[0] == '0') {
|
|
69
|
+
return 0, false
|
|
70
|
+
}
|
|
71
|
+
index, err := strconv.ParseUint(key, 10, 32)
|
|
72
|
+
return index, err == nil && index < 4294967295
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Keys returns the keys in the order of JavaScript
|
|
76
|
+
func (r *Record[V]) Keys() []string {
|
|
77
|
+
var indexes []string
|
|
78
|
+
var others []string
|
|
79
|
+
for _, key := range r.keys {
|
|
80
|
+
if _, ok := arrayIndex(key); ok {
|
|
81
|
+
indexes = append(indexes, key)
|
|
82
|
+
} else {
|
|
83
|
+
others = append(others, key)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if len(indexes) == 0 {
|
|
87
|
+
return others
|
|
88
|
+
}
|
|
89
|
+
sort.Slice(indexes, func(i, j int) bool {
|
|
90
|
+
a, _ := arrayIndex(indexes[i])
|
|
91
|
+
b, _ := arrayIndex(indexes[j])
|
|
92
|
+
return a < b
|
|
93
|
+
})
|
|
94
|
+
return append(indexes, others...)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Values returns the values in the order of the keys
|
|
98
|
+
func (r *Record[V]) Values() []V {
|
|
99
|
+
keys := r.Keys()
|
|
100
|
+
values := make([]V, len(keys))
|
|
101
|
+
for i, key := range keys {
|
|
102
|
+
values[i] = r.values[key]
|
|
103
|
+
}
|
|
104
|
+
return values
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Entries returns the entries in the order of the keys
|
|
108
|
+
func (r *Record[V]) Entries() []Entry[string, V] {
|
|
109
|
+
keys := r.Keys()
|
|
110
|
+
entries := make([]Entry[string, V], len(keys))
|
|
111
|
+
for i, key := range keys {
|
|
112
|
+
entries[i] = Entry[string, V]{key, r.values[key]}
|
|
113
|
+
}
|
|
114
|
+
return entries
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MarshalJSON writes the record like JSON.stringify
|
|
118
|
+
func (r *Record[V]) MarshalJSON() ([]byte, error) {
|
|
119
|
+
return []byte(Stringify(r)), nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func (r *Record[V]) writeJSON(writer *jsonWriter) {
|
|
123
|
+
writer.out.WriteByte('{')
|
|
124
|
+
for i, key := range r.Keys() {
|
|
125
|
+
if i > 0 {
|
|
126
|
+
writer.out.WriteByte(',')
|
|
127
|
+
}
|
|
128
|
+
writer.writeString(key)
|
|
129
|
+
writer.out.WriteByte(':')
|
|
130
|
+
writer.write(r.values[key])
|
|
131
|
+
}
|
|
132
|
+
writer.out.WriteByte('}')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Map is a Map of JavaScript: the keys are in the order they were added
|
|
136
|
+
type Map[K comparable, V any] struct {
|
|
137
|
+
keys []K
|
|
138
|
+
values map[K]V
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// NewMap returns an empty map
|
|
142
|
+
func NewMap[K comparable, V any]() *Map[K, V] {
|
|
143
|
+
return &Map[K, V]{values: map[K]V{}}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get returns the value of a key, or the zero value if the key is missing
|
|
147
|
+
func (m *Map[K, V]) Get(key K) V {
|
|
148
|
+
return m.values[key]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// GetOr returns the value of a key, or the fallback if the key is missing
|
|
152
|
+
func (m *Map[K, V]) GetOr(key K, fallback V) V {
|
|
153
|
+
if value, ok := m.values[key]; ok {
|
|
154
|
+
return value
|
|
155
|
+
}
|
|
156
|
+
return fallback
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Set adds or replaces the value of a key
|
|
160
|
+
func (m *Map[K, V]) Set(key K, value V) {
|
|
161
|
+
if _, ok := m.values[key]; !ok {
|
|
162
|
+
m.keys = append(m.keys, key)
|
|
163
|
+
}
|
|
164
|
+
m.values[key] = value
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Has returns whether the map has a key
|
|
168
|
+
func (m *Map[K, V]) Has(key K) bool {
|
|
169
|
+
_, ok := m.values[key]
|
|
170
|
+
return ok
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Len returns the number of keys
|
|
174
|
+
func (m *Map[K, V]) Len() int {
|
|
175
|
+
return len(m.keys)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Entries returns the entries in the order they were added
|
|
179
|
+
func (m *Map[K, V]) Entries() []Entry[K, V] {
|
|
180
|
+
entries := make([]Entry[K, V], len(m.keys))
|
|
181
|
+
for i, key := range m.keys {
|
|
182
|
+
entries[i] = Entry[K, V]{key, m.values[key]}
|
|
183
|
+
}
|
|
184
|
+
return entries
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Set is a Set of JavaScript: the values are in the order they were added
|
|
188
|
+
type Set[T comparable] struct {
|
|
189
|
+
values []T
|
|
190
|
+
index map[T]bool
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// NewSet returns an empty set
|
|
194
|
+
func NewSet[T comparable]() *Set[T] {
|
|
195
|
+
return &Set[T]{index: map[T]bool{}}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add adds a value, unless the set has it
|
|
199
|
+
func (s *Set[T]) Add(value T) {
|
|
200
|
+
if !s.index[value] {
|
|
201
|
+
s.index[value] = true
|
|
202
|
+
s.values = append(s.values, value)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Has returns whether the set has a value
|
|
207
|
+
func (s *Set[T]) Has(value T) bool {
|
|
208
|
+
return s.index[value]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Len returns the number of values
|
|
212
|
+
func (s *Set[T]) Len() int {
|
|
213
|
+
return len(s.values)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Values returns the values in the order they were added
|
|
217
|
+
func (s *Set[T]) Values() []T {
|
|
218
|
+
return append([]T(nil), s.values...)
|
|
219
|
+
}
|