@8bitscript/compiler 0.1.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/LICENSE +21 -0
- package/index.mjs +91 -0
- package/package.json +29 -0
- package/src/ast/index.mjs +90 -0
- package/src/checker/index.mjs +294 -0
- package/src/diagnostics/index.mjs +179 -0
- package/src/fold/facts.mjs +209 -0
- package/src/fold/index.mjs +412 -0
- package/src/intellisense/index.mjs +558 -0
- package/src/ir/index.mjs +1422 -0
- package/src/lexer/index.mjs +383 -0
- package/src/linker/hazards.mjs +121 -0
- package/src/linker/index.mjs +1075 -0
- package/src/parser/index.mjs +795 -0
- package/src/resolver/index.mjs +534 -0
- package/src/templates/index.mjs +278 -0
- package/src/types/index.mjs +116 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// Template layout: `text.print(cell, \`TICK ${ticks % 10:1} OPTION ${option}\`)`
|
|
2
|
+
// split into the pieces a person would have written by hand — one run of
|
|
3
|
+
// text and one number field at a time, each at the cell the pieces before
|
|
4
|
+
// it add up to.
|
|
5
|
+
//
|
|
6
|
+
// One implementation, two callers. The checker (packages/compiler/src/
|
|
7
|
+
// checker) runs it so a template's shape and field-width problems reach
|
|
8
|
+
// the editor — `analyze()` never lowers, and the editor only sees what
|
|
9
|
+
// `analyze()` reports. Lowering (packages/compiler/src/ir) runs it again
|
|
10
|
+
// to build the calls, and refuses on the same problems so a direct
|
|
11
|
+
// `lower()` can never silently drop a template. The linker deduplicates
|
|
12
|
+
// the two reports of one problem by code and span.
|
|
13
|
+
//
|
|
14
|
+
// What both need to know about names, without a binder: the types of the
|
|
15
|
+
// current module's own globals and functions (scanDeclaredTypes) and of
|
|
16
|
+
// the enclosing function's parameters. That is enough to size a field from
|
|
17
|
+
// its expression — `utinyint` is three digits, `usmallint` five — and
|
|
18
|
+
// honest about the rest: an imported name, a namespace member, or a call
|
|
19
|
+
// across modules has no visible type, and such a field needs its width
|
|
20
|
+
// written (`${x:3}`) rather than guessed.
|
|
21
|
+
import { Codes, diagnostic } from '../diagnostics/index.mjs';
|
|
22
|
+
import { NodeType } from '../ast/index.mjs';
|
|
23
|
+
import { resolveIntegerType } from '../types/index.mjs';
|
|
24
|
+
|
|
25
|
+
/** A string holds at most this many characters (one length byte). */
|
|
26
|
+
export const MAX_STRING_LENGTH = 255;
|
|
27
|
+
|
|
28
|
+
/** `void` / `bool` / `string` / an integer type, or null when `name` is none of them. */
|
|
29
|
+
export function resolveScalarType(name, { allowVoid = false, allowString = false } = {}) {
|
|
30
|
+
if (name === 'void') return allowVoid ? 'void' : null;
|
|
31
|
+
if (name === 'string') return allowString ? 'string' : null;
|
|
32
|
+
if (name === 'bool') return 'bool';
|
|
33
|
+
const resolved = resolveIntegerType(name);
|
|
34
|
+
return resolved ? resolved.canonicalName : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Elements an array may hold at most: an index has to fit the machine's address arithmetic. */
|
|
38
|
+
export const MAX_ARRAY_LENGTH = 65535;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `array<T, N>` resolved: the element type and the length, or a reason it
|
|
42
|
+
* cannot be. `N` is a literal or a const in `consts` (name to value) — a
|
|
43
|
+
* const is resolved by 8bitscript, so it can size a type as well as a
|
|
44
|
+
* literal can.
|
|
45
|
+
*
|
|
46
|
+
* @returns {{ type: string, length: number } | { error: string }}
|
|
47
|
+
*/
|
|
48
|
+
export function resolveArrayType(annotation, consts = new Map()) {
|
|
49
|
+
const [element, size] = annotation?.typeArguments ?? [];
|
|
50
|
+
const type = element?.name && resolveScalarType(element.name);
|
|
51
|
+
if (!element || !type || element.typeArguments?.length) {
|
|
52
|
+
return { error: 'array<T, N> needs an integer or bool element type T' };
|
|
53
|
+
}
|
|
54
|
+
let length = null;
|
|
55
|
+
if (size?.type === NodeType.IntegerLiteral) length = size.value;
|
|
56
|
+
else if (size?.type === NodeType.TypeReference && !size.typeArguments?.length && consts.has(size.name)) {
|
|
57
|
+
length = consts.get(size.name);
|
|
58
|
+
}
|
|
59
|
+
if (length === null) {
|
|
60
|
+
return { error: 'array<T, N> needs a length N: an integer literal, or a const declared in this module' };
|
|
61
|
+
}
|
|
62
|
+
if (!Number.isInteger(length) || length < 1 || length > MAX_ARRAY_LENGTH) {
|
|
63
|
+
return { error: `an array length must be 1..${MAX_ARRAY_LENGTH}, not ${length}` };
|
|
64
|
+
}
|
|
65
|
+
return { type, length };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The type a `.length` folds to: the smallest unsigned type the number fits. */
|
|
69
|
+
export function typeForCount(n) {
|
|
70
|
+
return n <= 255 ? 'utinyint' : 'usmallint';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Decimal digits the widest value of an unsigned type needs: 255 -> 3, 65535 -> 5. */
|
|
74
|
+
export function digitsFor(typeName) {
|
|
75
|
+
const type = resolveIntegerType(typeName);
|
|
76
|
+
if (!type || type.signed || type.bits > 16) return null;
|
|
77
|
+
return String(type.max).length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Widest of two integer types, for the result of arithmetic on them. */
|
|
81
|
+
function widerOf(a, b) {
|
|
82
|
+
const ta = resolveIntegerType(a);
|
|
83
|
+
const tb = resolveIntegerType(b);
|
|
84
|
+
if (!ta || !tb) return null;
|
|
85
|
+
return ta.bits >= tb.bits ? ta.canonicalName : tb.canonicalName;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const COMPARISON_OPERATORS = new Set(['==', '!=', '<', '>', '<=', '>=', '&&', '||']);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The declared types of a module's top-level globals, consts, and
|
|
92
|
+
* functions (return types), keyed by name — what a field expression's
|
|
93
|
+
* type can be read from without a binder. Arrays are listed apart, with
|
|
94
|
+
* their element type, length, and whether they are `const` (data in the
|
|
95
|
+
* program) or `let` (RAM): `a[i]` has the element type, `a.length` is a
|
|
96
|
+
* number, and `a` on its own is neither.
|
|
97
|
+
*
|
|
98
|
+
* @returns {{ globalTypes: Map<string,string>, functionTypes: Map<string,string>,
|
|
99
|
+
* arrayTypes: Map<string,{ type: string, length: number, constant: boolean }>,
|
|
100
|
+
* functionArity: Map<string,{ min: number, max: number }> }}
|
|
101
|
+
*/
|
|
102
|
+
export function scanDeclaredTypes(ast) {
|
|
103
|
+
const globalTypes = new Map();
|
|
104
|
+
const functionTypes = new Map();
|
|
105
|
+
const arrayTypes = new Map();
|
|
106
|
+
// How many arguments each own function takes: `max` is its parameters,
|
|
107
|
+
// `min` those without a default.
|
|
108
|
+
const functionArity = new Map();
|
|
109
|
+
// Literal consts, in source order, so `array<u8, COUNT>` can be sized.
|
|
110
|
+
const consts = new Map();
|
|
111
|
+
for (const node of ast?.body ?? []) {
|
|
112
|
+
if (node.type === NodeType.VariableDeclaration && node.name && node.typeAnnotation) {
|
|
113
|
+
if (node.kind === 'const' && node.initializer?.type === NodeType.IntegerLiteral) {
|
|
114
|
+
consts.set(node.name.name, node.initializer.value);
|
|
115
|
+
}
|
|
116
|
+
if (node.typeAnnotation.name === 'array') {
|
|
117
|
+
const resolved = resolveArrayType(node.typeAnnotation, consts);
|
|
118
|
+
if (!resolved.error) arrayTypes.set(node.name.name, { ...resolved, constant: node.kind === 'const' });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (node.typeAnnotation.name === 'string') {
|
|
122
|
+
// A string const or a string<N>: `.length` is a byte either way.
|
|
123
|
+
globalTypes.set(node.name.name, 'string');
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const type = resolveScalarType(
|
|
127
|
+
node.typeAnnotation.name === 'volatile'
|
|
128
|
+
? (node.typeAnnotation.typeArguments?.[0]?.name ?? '')
|
|
129
|
+
: node.typeAnnotation.name,
|
|
130
|
+
);
|
|
131
|
+
if (type) globalTypes.set(node.name.name, type);
|
|
132
|
+
}
|
|
133
|
+
if (node.type === NodeType.FunctionDeclaration && node.name) {
|
|
134
|
+
const type = resolveScalarType(node.returnType?.name ?? 'void', { allowVoid: true });
|
|
135
|
+
if (type) functionTypes.set(node.name.name, type);
|
|
136
|
+
const params = node.params ?? [];
|
|
137
|
+
functionArity.set(node.name.name, { max: params.length, min: params.filter((p) => !p.defaultValue).length });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { globalTypes, functionTypes, arrayTypes, functionArity };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The parameter types of a function declaration, keyed by name — the
|
|
145
|
+
* innermost scope a field expression can name.
|
|
146
|
+
*/
|
|
147
|
+
export function parameterTypes(fn) {
|
|
148
|
+
return new Map((fn.params ?? []).flatMap((p) => {
|
|
149
|
+
const type = p.typeAnnotation?.name && resolveScalarType(p.typeAnnotation.name, { allowString: true });
|
|
150
|
+
return type ? [[p.name.name, type]] : [];
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The static type of an expression, as far as a module can tell on its
|
|
156
|
+
* own: literals, the enclosing function's parameters, the module's own
|
|
157
|
+
* globals and functions, and arithmetic over those. Null for anything it
|
|
158
|
+
* cannot see.
|
|
159
|
+
*
|
|
160
|
+
* @param {{ paramTypes: Map, globalTypes: Map, functionTypes: Map, arrayTypes?: Map }} scope
|
|
161
|
+
*/
|
|
162
|
+
export function inferType(node, scope) {
|
|
163
|
+
const arrayOf = (object) => (object?.type === NodeType.Identifier && !scope.paramTypes.has(object.name)
|
|
164
|
+
? (scope.arrayTypes?.get(object.name) ?? null)
|
|
165
|
+
: null);
|
|
166
|
+
switch (node.type) {
|
|
167
|
+
case NodeType.IntegerLiteral:
|
|
168
|
+
if (node.value <= 255) return 'utinyint';
|
|
169
|
+
if (node.value <= 65535) return 'usmallint';
|
|
170
|
+
return 'uint';
|
|
171
|
+
case NodeType.BooleanLiteral:
|
|
172
|
+
return 'bool';
|
|
173
|
+
case NodeType.Identifier:
|
|
174
|
+
return scope.paramTypes.get(node.name) ?? scope.globalTypes.get(node.name) ?? null;
|
|
175
|
+
case NodeType.BinaryExpression: {
|
|
176
|
+
if (COMPARISON_OPERATORS.has(node.operator)) return 'bool';
|
|
177
|
+
return widerOf(inferType(node.left, scope), inferType(node.right, scope));
|
|
178
|
+
}
|
|
179
|
+
case NodeType.UnaryExpression:
|
|
180
|
+
return node.operator === '!' ? 'bool' : inferType(node.argument, scope);
|
|
181
|
+
case NodeType.IndexExpression:
|
|
182
|
+
return arrayOf(node.object)?.type ?? null;
|
|
183
|
+
case NodeType.MemberExpression: {
|
|
184
|
+
if (node.property?.name !== 'length') return null;
|
|
185
|
+
const array = arrayOf(node.object);
|
|
186
|
+
if (array) return typeForCount(array.length);
|
|
187
|
+
// `s.length` on a string — a parameter, const, or string<N> — is a byte.
|
|
188
|
+
return node.object?.type === NodeType.Identifier && inferType(node.object, scope) === 'string' ? 'utinyint' : null;
|
|
189
|
+
}
|
|
190
|
+
case NodeType.CallExpression:
|
|
191
|
+
return node.callee.type === NodeType.Identifier && !node.callee.compileTime
|
|
192
|
+
? (scope.functionTypes.get(node.callee.name) ?? null)
|
|
193
|
+
: null;
|
|
194
|
+
default:
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Is this call the one shape a template may appear in —
|
|
201
|
+
* `namespace.print(cell, \`...\`)`, exactly two arguments, the second a
|
|
202
|
+
* template? (Whether it is in statement position is the caller's to
|
|
203
|
+
* know: the checker tracks it while walking, lowering only reaches
|
|
204
|
+
* templateCall() from a statement.)
|
|
205
|
+
*/
|
|
206
|
+
export function isTemplateCall(node) {
|
|
207
|
+
return node?.type === NodeType.CallExpression
|
|
208
|
+
&& node.callee?.type === NodeType.MemberExpression
|
|
209
|
+
&& node.callee.object?.type === NodeType.Identifier
|
|
210
|
+
&& node.callee.property?.name === 'print'
|
|
211
|
+
&& node.args.length === 2
|
|
212
|
+
&& node.args[1]?.type === NodeType.TemplateLiteral;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The MISPLACED_TEMPLATE diagnostic for a template anywhere else. */
|
|
216
|
+
export function misplacedTemplate(template, file, namespace = null) {
|
|
217
|
+
return diagnostic(
|
|
218
|
+
Codes.MISPLACED_TEMPLATE,
|
|
219
|
+
namespace
|
|
220
|
+
? `a template string is only valid as the second argument of ${namespace}.print(cell, ...)`
|
|
221
|
+
: "a template string is only valid as the second argument of a namespace's print(cell, ...)",
|
|
222
|
+
file, template.start, template.length,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Lay a template out. Returns the pieces in order, each with the cell
|
|
228
|
+
* offset it starts at, or the diagnostics that stopped it.
|
|
229
|
+
*
|
|
230
|
+
* @param {object} template TemplateLiteral node.
|
|
231
|
+
* @param {{ paramTypes: Map, globalTypes: Map, functionTypes: Map }} scope
|
|
232
|
+
* @param {string} file
|
|
233
|
+
* @param {string|null} source The file's text, so a diagnostic can quote a field back.
|
|
234
|
+
* @returns {{ pieces: ({ kind: 'text', offset: number, node: object }
|
|
235
|
+
* | { kind: 'field', offset: number, width: number, node: object })[], diagnostics: object[] }}
|
|
236
|
+
*/
|
|
237
|
+
export function layoutTemplate(template, scope, file, source = null) {
|
|
238
|
+
const pieces = [];
|
|
239
|
+
const diagnostics = [];
|
|
240
|
+
let offset = 0;
|
|
241
|
+
for (const part of template.parts) {
|
|
242
|
+
if (part.type === NodeType.TemplateText) {
|
|
243
|
+
pieces.push({ kind: 'text', offset, node: part });
|
|
244
|
+
offset += part.value.length;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
let width;
|
|
248
|
+
if (part.width) {
|
|
249
|
+
width = part.width.value;
|
|
250
|
+
if (width < 1 || width > MAX_STRING_LENGTH) {
|
|
251
|
+
diagnostics.push(diagnostic(
|
|
252
|
+
Codes.UNPRINTABLE_FIELD, `a field width must be 1..${MAX_STRING_LENGTH}, not ${width}`,
|
|
253
|
+
file, part.width.start, part.width.length,
|
|
254
|
+
));
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
const type = inferType(part.expression, scope);
|
|
259
|
+
width = type ? digitsFor(type) : null;
|
|
260
|
+
if (width === null) {
|
|
261
|
+
const quoted = source
|
|
262
|
+
? source.slice(part.expression.start, part.expression.start + part.expression.length)
|
|
263
|
+
: '...';
|
|
264
|
+
diagnostics.push(diagnostic(
|
|
265
|
+
Codes.UNPRINTABLE_FIELD,
|
|
266
|
+
type
|
|
267
|
+
? `a ${type} cannot be a number field: fields show unsigned values up to 16 bits (utinyint, usmallint)`
|
|
268
|
+
: `this field's width cannot be taken from its expression here; say it: \${${quoted}:3}`,
|
|
269
|
+
file, part.start, part.length,
|
|
270
|
+
));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
pieces.push({ kind: 'field', offset, width, node: part });
|
|
275
|
+
offset += width;
|
|
276
|
+
}
|
|
277
|
+
return { pieces, diagnostics };
|
|
278
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// The primitive integer type registry.
|
|
2
|
+
//
|
|
3
|
+
// One authoritative description of `utinyint`/`u8` and its seven siblings, so
|
|
4
|
+
// nothing downstream keeps its own copy. Before this module existed, the
|
|
5
|
+
// lexer's type-name set, the checker's range table, and each backend's width
|
|
6
|
+
// map were four hand-written lists that had to be kept in lockstep by hand.
|
|
7
|
+
// Now they all read this one.
|
|
8
|
+
//
|
|
9
|
+
// Naming is MySQL-inspired on purpose: `tinyint`/`smallint`/`mediumint`/`int`
|
|
10
|
+
// (and their `u`-prefixed unsigned counterparts) read as storage sizes to
|
|
11
|
+
// someone who has never seen `i8`/`u32`-style abbreviations, which is exactly
|
|
12
|
+
// the audience this is for. `i8`, `u8`, and so on remain as low-level aliases
|
|
13
|
+
// — the systems-programming spelling stays available, it just is not what the
|
|
14
|
+
// language leads with. `bigint`/`ubigint` are reserved for a future 64-bit
|
|
15
|
+
// type and deliberately not recognised anywhere yet.
|
|
16
|
+
//
|
|
17
|
+
// The canonical name IS the internal id: the IR and both backends key their
|
|
18
|
+
// own data by `canonicalName` (`utinyint`, not `u8`). `utinyint` and `u8` are
|
|
19
|
+
// never two types that happen to agree; they resolve to the same descriptor
|
|
20
|
+
// before anything downstream ever sees them.
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} IntegerType
|
|
24
|
+
* @property {string} canonicalName Preferred, human-readable spelling (e.g. "utinyint") —
|
|
25
|
+
* also the id every compiler stage stores internally.
|
|
26
|
+
* @property {string} legacyAlias The short systems-programming spelling (e.g. "u8").
|
|
27
|
+
* @property {string[]} aliases Every spelling other than the canonical one.
|
|
28
|
+
* @property {boolean} signed
|
|
29
|
+
* @property {number} bits
|
|
30
|
+
* @property {number} bytes
|
|
31
|
+
* @property {number} min Inclusive.
|
|
32
|
+
* @property {number} max Inclusive.
|
|
33
|
+
* @property {string} summary One sentence, e.g. "Unsigned 1-byte integer."
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** Signed base name by bit width; the unsigned spelling is always `u` + this. */
|
|
37
|
+
const WIDTH_NAME = { 8: 'tinyint', 16: 'smallint', 24: 'mediumint', 32: 'int' };
|
|
38
|
+
|
|
39
|
+
function makeIntegerType(bits, signed) {
|
|
40
|
+
const range = 2 ** bits;
|
|
41
|
+
const bytes = bits / 8;
|
|
42
|
+
const base = WIDTH_NAME[bits];
|
|
43
|
+
const canonicalName = signed ? base : `u${base}`;
|
|
44
|
+
const legacyAlias = `${signed ? 'i' : 'u'}${bits}`;
|
|
45
|
+
return {
|
|
46
|
+
canonicalName,
|
|
47
|
+
legacyAlias,
|
|
48
|
+
aliases: [legacyAlias],
|
|
49
|
+
signed,
|
|
50
|
+
bits,
|
|
51
|
+
bytes,
|
|
52
|
+
min: signed ? -(range / 2) : 0,
|
|
53
|
+
max: signed ? range / 2 - 1 : range - 1,
|
|
54
|
+
summary: `${signed ? 'Signed' : 'Unsigned'} ${bytes}-byte integer.`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Every canonical primitive integer type, narrowest first and signed before
|
|
60
|
+
* unsigned within a width — the order completion offers them in:
|
|
61
|
+
* tinyint, utinyint, smallint, usmallint, mediumint, umediumint, int, uint.
|
|
62
|
+
*/
|
|
63
|
+
export const PRIMITIVE_INTEGER_TYPES = [8, 16, 24, 32].flatMap(
|
|
64
|
+
(bits) => [makeIntegerType(bits, true), makeIntegerType(bits, false)],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const BY_SPELLING = new Map();
|
|
68
|
+
for (const type of PRIMITIVE_INTEGER_TYPES) {
|
|
69
|
+
BY_SPELLING.set(type.canonicalName, type);
|
|
70
|
+
for (const alias of type.aliases) BY_SPELLING.set(alias, type);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Bytes one value of a type takes in the target's memory: what an array's
|
|
75
|
+
* or a variable's RAM comes to. The 24-bit types widen to 4 bytes, since
|
|
76
|
+
* neither backend has a 3-byte integer (see their NATIVE_WIDTH tables);
|
|
77
|
+
* `bool` is a byte; a `string` is a pointer, 2 bytes on a 6502 (the web's
|
|
78
|
+
* 4-byte `usize` is not RAM a program counts).
|
|
79
|
+
*
|
|
80
|
+
* @param {string} name A canonical type name or alias, `bool`, or `string`.
|
|
81
|
+
* @returns {number}
|
|
82
|
+
*/
|
|
83
|
+
export function storageBytes(name) {
|
|
84
|
+
if (name === 'bool') return 1;
|
|
85
|
+
if (name === 'string') return 2;
|
|
86
|
+
const type = BY_SPELLING.get(name);
|
|
87
|
+
if (!type) return 0;
|
|
88
|
+
return type.bits === 24 ? 4 : type.bits / 8;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Every spelling a primitive integer type can be written with, canonical or alias. */
|
|
92
|
+
export const INTEGER_TYPE_NAMES = [...BY_SPELLING.keys()];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve any spelling — canonical (`utinyint`) or legacy alias (`u8`) — to
|
|
96
|
+
* its descriptor. Two different spellings of the same type return the same
|
|
97
|
+
* object, which is what "not a separate type internally" means in practice.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} name
|
|
100
|
+
* @returns {IntegerType | undefined}
|
|
101
|
+
*/
|
|
102
|
+
export function resolveIntegerType(name) {
|
|
103
|
+
return BY_SPELLING.get(name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Inclusive `[min, max]` ranges, keyed by every recognised spelling. Kept for
|
|
108
|
+
* callers that only ever needed bounds — the checker's original shape, before
|
|
109
|
+
* it had a reason to ask for anything else.
|
|
110
|
+
*/
|
|
111
|
+
export const INTEGER_RANGES = Object.fromEntries(
|
|
112
|
+
INTEGER_TYPE_NAMES.map((name) => {
|
|
113
|
+
const type = resolveIntegerType(name);
|
|
114
|
+
return [name, [type.min, type.max]];
|
|
115
|
+
}),
|
|
116
|
+
);
|