@temperlang/core 0.4.0 → 0.6.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/check-type.js CHANGED
@@ -27,17 +27,6 @@ export const requireInstanceOf = (x, typeRequirement) => {
27
27
  export const requireNotNull = (x) =>
28
28
  x == null ? bubble() : x
29
29
 
30
- /**
31
- * @param {number} x
32
- * @returns {number}
33
- */
34
- export const requireIsSafeInteger = (x) => {
35
- if (!Number.isSafeInteger(x)) {
36
- bubble();
37
- }
38
- return x;
39
- };
40
-
41
30
  /**
42
31
  * @template X, Y
43
32
  * @param {X} x
package/core.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
 
2
3
  /**
3
4
  * @param {number} x
@@ -10,7 +11,7 @@ export const divIntInt = (x, y) => {
10
11
  bubble();
11
12
  }
12
13
  /* not NaN or infinite */
13
- return result;
14
+ return result | 0;
14
15
  };
15
16
 
16
17
  /**
@@ -25,9 +26,137 @@ export const modIntInt = (x, y) => {
25
26
  bubble();
26
27
  }
27
28
  /* not NaN or infinite */
28
- return result;
29
+ return result | 0;
29
30
  };
30
31
 
32
+ /**
33
+ * @param {string} s
34
+ * @param {(number | bigint)?} radix
35
+ * @returns {bigint}
36
+ */
37
+ export const parseBigInt = (s, radix) => {
38
+ const rdx = BigInt(radix ?? 10);
39
+ let result = 0n;
40
+ // TODO Definitions of whitespace? Better to check /\s/ on the fly?
41
+ s = s.trim();
42
+ let i = 0;
43
+ let sign = 1n;
44
+ if (s.startsWith("-")) {
45
+ i++;
46
+ sign = -1n;
47
+ }
48
+ for (; i < s.length; i++) {
49
+ const c = s.charCodeAt(i);
50
+ /** @type {bigint} */
51
+ let digit;
52
+ if (c >= c0 && c <= c9) {
53
+ digit = BigInt(c - c0);
54
+ } else if (c >= cA && c <= cZ) {
55
+ digit = BigInt(c - cA + 10);
56
+ } else if (c >= ca && c <= cz) {
57
+ digit = BigInt(c - ca + 10);
58
+ } else {
59
+ bubble();
60
+ }
61
+ if (digit >= rdx) {
62
+ bubble();
63
+ }
64
+ result = result * rdx + digit;
65
+ }
66
+ return sign * result;
67
+ };
68
+
69
+ const c0 = "0".charCodeAt(0);
70
+ const c9 = "9".charCodeAt(0);
71
+ const cA = "A".charCodeAt(0);
72
+ const cZ = "Z".charCodeAt(0);
73
+ const ca = "a".charCodeAt(0);
74
+ const cz = "z".charCodeAt(0);
75
+
76
+ export const INT32_MAX = (2 ** 31) - 1;
77
+ export const INT32_MIN = -(2 ** 31);
78
+ export const INT64_MAX = (2n ** 63n) - 1n;
79
+ export const INT64_MIN = -(2n ** 63n);
80
+
81
+ // Related to int64 limits above.
82
+ const edge64 = 1n << 64n;
83
+
84
+ /**
85
+ * @param {bigint} x
86
+ * @returns {bigint}
87
+ */
88
+ export const clampInt64 = (x) => {
89
+ x = x % edge64;
90
+ if (x > INT64_MAX) {
91
+ x -= edge64;
92
+ } else if (x < INT64_MIN) {
93
+ x += edge64;
94
+ }
95
+ return x;
96
+ };
97
+
98
+ /**
99
+ * Implements extension method Int64::max
100
+ * @param {bigint} a
101
+ * @param {bigint} b
102
+ * @returns {bigint}
103
+ */
104
+ export const int64Max = (a, b) => {
105
+ return a > b ? a : b;
106
+ }
107
+
108
+ /**
109
+ * Implements extension method Int64::min
110
+ * @param {bigint} a
111
+ * @param {bigint} b
112
+ * @returns {bigint}
113
+ */
114
+ export const int64Min = (a, b) => {
115
+ return a < b ? a : b;
116
+ }
117
+
118
+ /**
119
+ * Implements extension method Int64::toInt32
120
+ * @param {bigint} n
121
+ * @returns {number}
122
+ */
123
+ export const int64ToInt32 = (n) => {
124
+ if (n < INT32_MIN || n > INT32_MAX) {
125
+ bubble();
126
+ }
127
+ return int64ToInt32Unsafe(n);
128
+ }
129
+
130
+ /**
131
+ * Implements extension method Int64::toInt32Unsafe
132
+ * @param {bigint} n
133
+ * @returns {number}
134
+ */
135
+ export const int64ToInt32Unsafe = (n) => {
136
+ return Number(n) | 0;
137
+ }
138
+
139
+ /**
140
+ * Implements extension method Int64::toFloat64
141
+ * @param {bigint} n
142
+ * @returns {number}
143
+ */
144
+ export const int64ToFloat64 = (n) => {
145
+ if (n < Number.MIN_SAFE_INTEGER || n > Number.MAX_SAFE_INTEGER) {
146
+ bubble();
147
+ }
148
+ return Number(n);
149
+ }
150
+
151
+ /**
152
+ * Implements extension method Int64::toFloat63Unsafe
153
+ * @param {bigint} n
154
+ * @returns {number}
155
+ */
156
+ export const int64ToFloat64Unsafe = (n) => {
157
+ return Number(n);
158
+ }
159
+
31
160
  /**
32
161
  * Compare two Strings.
33
162
  * @param {string} a
@@ -64,9 +193,11 @@ export const cmpFloat = (a, b) => {
64
193
  return 0;
65
194
  }
66
195
  if (a === b) {
196
+ // @ts-ignore
67
197
  return Object.is(a, 0) - Object.is(b, 0);
68
198
  }
69
199
  if (isNaN(a) || isNaN(b)) {
200
+ // @ts-ignore
70
201
  return isNaN(a) - isNaN(b);
71
202
  }
72
203
  return a - b;
@@ -86,6 +217,7 @@ export const cmpGeneric = (a, b) => {
86
217
  return cmpFloat(a, b);
87
218
  }
88
219
  if (typeof a === "boolean" && typeof b === "boolean") {
220
+ // @ts-ignore
89
221
  return a - b;
90
222
  }
91
223
  bubble();
@@ -98,6 +230,13 @@ export const bubble = () => {
98
230
  throw Error();
99
231
  };
100
232
 
233
+ /**
234
+ * TODO Distinguish panic from bubble.
235
+ *
236
+ * @returns {never}
237
+ */
238
+ export const panic = bubble;
239
+
101
240
  /**
102
241
  * @template T
103
242
  * @param {T} a
@@ -105,3 +244,76 @@ export const bubble = () => {
105
244
  export const print = (a) => {
106
245
  console.log("%s", a);
107
246
  };
247
+
248
+ /**
249
+ * Takes a JSON adapter and a value that it can adapt.
250
+ * This is called when JavaScript code calls JSON.stringify on a Temper type instance
251
+ * that has a zero argument jsonAdapter static method.
252
+ *
253
+ * @return any
254
+ */
255
+ export let marshalToJsonObject = (jsonAdapter, value) => {
256
+ /** @type {any[]} */
257
+ const stack = [[]];
258
+ let pendingKey = null;
259
+ function store(value) {
260
+ let top = stack[stack.length - 1];
261
+ if (pendingKey !== null) {
262
+ top[pendingKey] = value;
263
+ pendingKey = null;
264
+ } else if (Array.isArray(top)) {
265
+ top.push(value);
266
+ } else {
267
+ throw new Error();
268
+ }
269
+ }
270
+ let jsonProducer = {
271
+ interchangeContext: { getHeader() { return null; } },
272
+
273
+ startObject() {
274
+ let o = {};
275
+ store(o);
276
+ stack.push(o);
277
+ },
278
+ endObject() { stack.pop(); },
279
+ objectKey(key) { pendingKey = String(key); },
280
+
281
+ startArray() {
282
+ let a = [];
283
+ store(a);
284
+ stack.push(a);
285
+ },
286
+ endArray() { stack.pop(); },
287
+
288
+ nullValue() { store(null); },
289
+ booleanValue(b) { store(!!b); },
290
+ int32Value(v) { store(Math.trunc(v)); },
291
+ float64Value(v) { store(+v); },
292
+ numericTokenValue(s) { store(+s); },
293
+ stringValue(s) { store(`${s}`); },
294
+
295
+ parseErrorReceiver: null,
296
+ }
297
+ jsonAdapter.encodeToJson(value, jsonProducer);
298
+ return stack[0][0];
299
+ };
300
+
301
+ /** @type {{}} */
302
+ let emptySingleton = Object.freeze(
303
+ // Prototype for empty
304
+ Object.create(
305
+ Object.freeze(
306
+ Object.create(
307
+ null,
308
+ {
309
+ toString: {
310
+ value: function toString() { return "(empty)" }
311
+ }
312
+ }
313
+ )
314
+ )
315
+ )
316
+ );
317
+
318
+ /** @return {{}} */
319
+ export function empty() { return emptySingleton }
package/float.js CHANGED
@@ -1,6 +1,7 @@
1
+ // @ts-check
1
2
 
2
3
  // Implements extension method Float64::near
3
- import {bubble} from "./core.js";
4
+ import {bubble, INT32_MAX, INT32_MIN, INT64_MAX, INT64_MIN} from "./core.js";
4
5
 
5
6
  /**
6
7
  * Implements extension method Float64::near
@@ -22,12 +23,12 @@ export const float64Near = (x, y, relTol, absTol) => {
22
23
  }
23
24
 
24
25
  /**
25
- * Implements extension method Float64::toInt
26
+ * Implements extension method Float64::toInt32
26
27
  * @param {number} n
27
28
  * @returns {number}
28
29
  */
29
- export const float64ToInt = (n) => {
30
- const i = float64ToIntUnsafe(n);
30
+ export const float64ToInt32 = (n) => {
31
+ const i = float64ToInt32Unsafe(n);
31
32
  if (Math.abs(n - i) < 1) {
32
33
  return i;
33
34
  } else {
@@ -36,20 +37,49 @@ export const float64ToInt = (n) => {
36
37
  }
37
38
 
38
39
  /**
39
- * Implements extension method Float64::toIntUnsafe
40
+ * Implements extension method Float64::toInt32Unsafe
40
41
  * @param {number} n
41
42
  * @returns {number}
42
43
  */
43
- export const float64ToIntUnsafe = (n) => {
44
+ export const float64ToInt32Unsafe = (n) => {
44
45
  // We are free to do whatever with NaN here.
45
46
  return isNaN(n)
46
47
  ? 0
47
48
  : Math.max(
48
- Number.MIN_SAFE_INTEGER,
49
- Math.min(Math.trunc(n), Number.MAX_SAFE_INTEGER)
49
+ INT32_MIN,
50
+ Math.min(Math.trunc(n), INT32_MAX)
50
51
  );
51
52
  }
52
53
 
54
+ /**
55
+ * Implements extension method Float64::toInt64
56
+ * @param {number} n
57
+ * @returns {bigint}
58
+ */
59
+ export const float64ToInt64 = (n) => {
60
+ // Also blocks NaNs.
61
+ if (!(n >= Number.MIN_SAFE_INTEGER && n <= Number.MAX_SAFE_INTEGER)) {
62
+ bubble();
63
+ }
64
+ return float64ToInt64Unsafe(n);
65
+ }
66
+
67
+ /**
68
+ * Implements extension method Float64::toInt64Unsafe
69
+ * @param {number} n
70
+ * @returns {bigint}
71
+ */
72
+ export const float64ToInt64Unsafe = (n) => {
73
+ // We are free to do whatever with NaN here.
74
+ return isNaN(n)
75
+ ? 0n
76
+ : BigInt(Math.max(
77
+ // Avoid converting giant numbers to bigint.
78
+ Number(INT64_MIN),
79
+ Math.min(Math.trunc(n), Number(INT64_MAX)),
80
+ ));
81
+ }
82
+
53
83
  /**
54
84
  * Implements extension method Float64::toString
55
85
  * @param {number} n
package/index.js CHANGED
@@ -8,6 +8,7 @@ export * from "./float.js";
8
8
  export * from "./interface.js";
9
9
  export * from "./listed.js";
10
10
  export * from "./mapped.js";
11
+ export * from "./net.js";
11
12
  export * from "./pair.js";
12
13
  export * from "./regex.js";
13
14
  export * from "./string.js";
package/interface.js CHANGED
@@ -37,7 +37,7 @@ export const type = (...superTypes) => {
37
37
 
38
38
  class Union {
39
39
  static [Symbol.hasInstance] = (instance) => {
40
- return key in instance;
40
+ return typeof instance === 'object' && instance !== null && key in instance;
41
41
  };
42
42
  }
43
43
 
package/mapped.js CHANGED
@@ -156,7 +156,7 @@ export const mappedHas = (map, key) => {
156
156
  * @returns {Readonly<K[]>}
157
157
  */
158
158
  export const mappedKeys = (map) => {
159
- return Object.freeze(Array.prototype.slice.call(map.keys()));
159
+ return Object.freeze(Array.from(map.keys()));
160
160
  }
161
161
 
162
162
  /**
@@ -165,7 +165,7 @@ export const mappedKeys = (map) => {
165
165
  * @returns {Readonly<V[]>}
166
166
  */
167
167
  export const mappedValues = (map) => {
168
- return Object.freeze(Array.prototype.slice.call(map.values()));
168
+ return Object.freeze(Array.from(map.values()));
169
169
  }
170
170
 
171
171
  /**
package/net.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @param {string} url
3
+ * @param {"GET" | "POST"} method
4
+ * @param {string | null} bodyContent
5
+ * @param {string | null} bodyMimeType
6
+ * @return {Promise<Response>}
7
+ */
8
+ export function stdNetSend(url, method, bodyContent, bodyMimeType) {
9
+ let details = { method };
10
+ if (typeof bodyContent === 'string') {
11
+ details.body = bodyContent;
12
+ if (typeof bodyMimeType === 'string') {
13
+ details.headers = { 'Content-Type': bodyMimeType };
14
+ }
15
+ }
16
+ return fetch(url, details);
17
+ }
18
+
19
+ /**
20
+ * @param {Response} r
21
+ * @return {number} an HTTP status code
22
+ */
23
+ export function netResponseGetStatus(r) {
24
+ return r.status;
25
+ }
26
+
27
+ /**
28
+ * @param {Response} r
29
+ * @return {string | null} the mime-type of the body.
30
+ */
31
+ export function netResponseGetContentType(r) {
32
+ return r.headers.get('Content-Type') || null;
33
+ }
34
+
35
+ /**
36
+ * @param {Response} r
37
+ * @return {Promise<string | null>} the body content.
38
+ */
39
+ export function netResponseGetBodyContent(r) {
40
+ return r.text();
41
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@temperlang/core",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Runtime support for JS generated by Temper",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "author": "https://github.com/mikesamuel",
8
- "license": "Apache-2.0"
8
+ "license": "Apache-2.0 OR MIT"
9
9
  }
package/regex.js CHANGED
@@ -108,7 +108,7 @@ export const regexCompiledSplit = (_, compiled, text) => {
108
108
  };
109
109
 
110
110
  /**
111
- * @param {RegExp} _
111
+ * @param {unknown} _
112
112
  * @param {string} formatted
113
113
  * @returns {RegExp}
114
114
  */
@@ -139,10 +139,16 @@ export const regexFormatterAdjustCodeSet = (self, codeSet, regexRefs) => {
139
139
  return codeSet;
140
140
  };
141
141
 
142
+ /**
143
+ * @param {unknown} _
144
+ * @param {[string]} out
145
+ * @param {number} code
146
+ * @param {boolean} insideCodeSet
147
+ */
142
148
  export const regexFormatterPushCodeTo = (_, out, code, insideCodeSet) => {
143
149
  // Ignore insideCodeSet for now.
144
150
  // TODO(tjp, regex): Get fancier, including with work in Temper.
145
- out.push(`\\u{${code.toString(16)}}`);
151
+ out[0] += `\\u{${code.toString(16)}}`;
146
152
  };
147
153
 
148
154
  // Cached later for some approximate efficiency.
package/string.js CHANGED
@@ -1,5 +1,16 @@
1
- import { requireIsSafeInteger } from "./check-type.js";
2
- import {bubble} from "./core.js";
1
+ import {
2
+ bubble, INT32_MAX, INT32_MIN, INT64_MAX, INT64_MIN, parseBigInt
3
+ } from "./core.js";
4
+
5
+ /**
6
+ * Implements extension method String::fromCodePoint
7
+ * @param {number} codePoint
8
+ * @returns {string}
9
+ */
10
+ export const stringFromCodePoint = (codePoint) => {
11
+ denySurrogate(codePoint);
12
+ return String.fromCodePoint(codePoint);
13
+ };
3
14
 
4
15
  /**
5
16
  * Implements extension method String::fromCodePoints
@@ -7,6 +18,9 @@ import {bubble} from "./core.js";
7
18
  * @returns {string}
8
19
  */
9
20
  export const stringFromCodePoints = (codePoints) => {
21
+ for (const codePoint of codePoints) {
22
+ denySurrogate(codePoint);
23
+ }
10
24
  // TODO Append in batches if codePoints is long?
11
25
  return String.fromCodePoint(...codePoints);
12
26
  };
@@ -44,20 +58,38 @@ export const stringToFloat64 = (s) => {
44
58
  };
45
59
 
46
60
  /**
47
- * Implements extension method String::toInt
61
+ * Implements extension method String::toInt32
48
62
  * @param {string} s
49
63
  * @param {number?} radix
50
64
  * @returns {number}
51
65
  */
52
- export const stringToInt = (s, radix) => {
66
+ export const stringToInt32 = (s, radix) => {
53
67
  // This currently maybe allocates for trim and then also for check.
54
68
  // TODO Avoid that with manual char checks? Arbitrary base makes regex harder.
55
69
  s = s.trim();
56
70
  radix = radix ?? 10;
57
71
  const result = parseInt(s, radix);
58
- requireIsSafeInteger(result);
72
+ // This check also catches nan.
73
+ if (!(result >= INT32_MIN && result <= INT32_MAX)) {
74
+ bubble();
75
+ }
59
76
  const trimmed = s.slice(0, s.length - 1);
60
77
  if (parseInt(trimmed, radix) === result) {
78
+ // Extraneous junk was ignored that we disallow.
79
+ bubble();
80
+ }
81
+ return result;
82
+ };
83
+
84
+ /**
85
+ * Implements extension method String::toInt64
86
+ * @param {string} s
87
+ * @param {number?} radix
88
+ * @returns {number}
89
+ */
90
+ export const stringToInt64 = (s, radix) => {
91
+ const result = parseBigInt(s, radix);
92
+ if (result < INT64_MIN || result > INT64_MAX) {
61
93
  bubble();
62
94
  }
63
95
  return result;
@@ -151,6 +183,25 @@ export const stringPrev = (s, i) => {
151
183
  return iPrev;
152
184
  };
153
185
 
186
+ /**
187
+ * @param {string} s
188
+ * @param {number} i
189
+ * @param {number} by
190
+ * @returns {number}
191
+ */
192
+ export const stringStep = (s, i, by) => {
193
+ let step = by >= 0 ? stringNext : stringPrev;
194
+ by = Math.abs(by);
195
+ for (let j = 0; j < by; j += 1) {
196
+ const iOld = i;
197
+ i = step(s, i);
198
+ if (i == iOld) {
199
+ break;
200
+ }
201
+ }
202
+ return i;
203
+ };
204
+
154
205
  /**
155
206
  * @param {string} s
156
207
  * @param {(number) => void} f
@@ -194,5 +245,15 @@ export const requireNoStringIndex = (i) => {
194
245
  * @param {number} c
195
246
  */
196
247
  export const stringBuilderAppendCodePoint = (s, c) => {
248
+ denySurrogate(c);
197
249
  s[0] += String.fromCodePoint(c);
198
250
  }
251
+
252
+ /**
253
+ * @param {number} c
254
+ */
255
+ const denySurrogate = (c) => {
256
+ if (c >= 0xD800 && c <= 0xDFFF) {
257
+ throw new RangeError(`Invalid Unicode scalar value ${c}`)
258
+ }
259
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "noEmit": true,
5
+ "preserveConstEnums": false,
6
+ "removeComments": true,
7
+ "sourceMap": false,
8
+ "target": "es2020"
9
+ },
10
+ "files": [
11
+ "async.js",
12
+ "bitvector.js",
13
+ "check-type.js",
14
+ "core.js",
15
+ "date.js",
16
+ "deque.js",
17
+ "float.js",
18
+ "index.js",
19
+ "interface.js",
20
+ "listed.js",
21
+ "mapped.js",
22
+ "pair.js",
23
+ "regex.js",
24
+ "string.js"
25
+ ]
26
+ }