soml-lang 0.0.1 → 0.0.3

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.
@@ -0,0 +1,102 @@
1
+ export declare const TAB = 9;
2
+ export declare const LF = 10;
3
+ export declare const SPACE = 32;
4
+ export declare const DOUBLE_QUOTE = 34;
5
+ export declare const HASH = 35;
6
+ export declare const SINGLE_QUOTE = 39;
7
+ export declare const ASTERISK = 42;
8
+ export declare const COMMA = 44;
9
+ export declare const DASH = 45;
10
+ export declare const DOT = 46;
11
+ export declare const SLASH = 47;
12
+ export declare const COLON = 58;
13
+ export declare const OPEN_BRACKET = 91;
14
+ export declare const BACKSLASH = 92;
15
+ export declare const CLOSE_BRACKET = 93;
16
+ export declare const OPEN_BRACE = 123;
17
+ export declare const CLOSE_BRACE = 125;
18
+ export declare const MAX_DEPTH = 100;
19
+ export declare const INT64_MIN: bigint;
20
+ export declare const INT64_MAX: bigint;
21
+ export declare const MIN_INSTANT = -62135596800000000000n;
22
+ export declare const MAX_INSTANT = 253402300799999999999n;
23
+ export declare function requireTemporal(): typeof globalThis.Temporal;
24
+ export declare const DURATION_UNITS: Map<string, bigint>;
25
+ /**
26
+ Remove the trailing zeros of a run of digits. A loop rather than a regular expression, which would be quadratic on a long run of zeros followed by another digit.
27
+ */
28
+ export declare function trimTrailingZeros(digits: string): string;
29
+ /**
30
+ A duration as a `Temporal.Duration` in hours and smaller units, which hold any int64 count of nanoseconds exactly.
31
+ */
32
+ export declare function createDuration(nanoseconds: bigint): Temporal.Duration;
33
+ /**
34
+ The `integers` option of `parse()`, `stringify()`, and `edit()`, checked, or its default when it is left out.
35
+ */
36
+ export declare function getIntegersOption(options: unknown): 'bigint' | 'number';
37
+ /**
38
+ Whether a UTF-16 code unit can be part of a bare key. `NaN`, which `charCodeAt()` returns past the end of a string, cannot.
39
+ */
40
+ export declare function isBareKeyCharacter(code: number): boolean;
41
+ /**
42
+ Whether a key can be written without quotes, as a bare key: one or more ASCII letters, digits, `_`, or `-`, in any order, so `404` and `-x` are bare keys too. `stringify()` writes such a key bare and quotes every other key.
43
+
44
+ @param key - The decoded key.
45
+ @returns Whether `key` can be a bare key.
46
+
47
+ @example
48
+ ```
49
+ import {isBareKey} from 'soml-lang';
50
+
51
+ isBareKey('content-type');
52
+ //=> true
53
+
54
+ isBareKey('a.b');
55
+ //=> false
56
+ ```
57
+ */
58
+ export declare function isBareKey(key: string): boolean;
59
+ /**
60
+ A key as an error message shows it: bare when it can be, and otherwise quoted, and cut short when it is long.
61
+ */
62
+ export declare function describeKey(key: string): string;
63
+ /**
64
+ The end of the number, instant, or duration token that starts at `start`, which is a digit, `-`, or the `i` of `infinity`.
65
+ */
66
+ export declare function findNumberEnd(source: string, start: number): number;
67
+ /**
68
+ The offset of the line feed that ends the line containing `offset`, or the length of `source` on the last line.
69
+ */
70
+ export declare function findLineEnd(source: string, offset: number): number;
71
+ /**
72
+ Find the closing delimiter of a block string: the first line from `offset` on that holds, after zero or more spaces and tabs, a run of exactly `length` of the `quote` character. A longer run is content. Returns where that line and its delimiter start, or `undefined` when no line closes the block.
73
+ */
74
+ export declare function findBlockStringEnd(source: string, offset: number, quote: number, length: number): {
75
+ lineStart: number;
76
+ delimiterStart: number;
77
+ } | undefined;
78
+ /**
79
+ Whether a UTF-16 code unit is a space or a tab, the whitespace within a line.
80
+ */
81
+ export declare function isSpace(code: number): boolean;
82
+ /**
83
+ The index after the spaces and tabs from `index` on.
84
+ */
85
+ export declare function skipSpaces(text: string, index: number): number;
86
+ /**
87
+ The index after the last character before `index` that is not a space or a tab.
88
+ */
89
+ export declare function skipSpacesBack(text: string, index: number): number;
90
+ /**
91
+ Whether a line in a block string is blank: empty, or only spaces and tabs. A blank line becomes an empty line in the value.
92
+ */
93
+ export declare function isBlankLine(line: string): boolean;
94
+ /**
95
+ Shorten text quoted in an error message, so a huge token does not make a huge message. The cut never splits a surrogate pair.
96
+ */
97
+ export declare function abbreviate(text: string, maximumLength?: number): string;
98
+ export declare function formatCodePoint(codePoint: number): string;
99
+ /**
100
+ Describe a character for an error message, such as `“$”` or `U+00A0 (NO-BREAK SPACE)`.
101
+ */
102
+ export declare function describeCharacter(codePoint: number): string;
@@ -0,0 +1,285 @@
1
+ /*
2
+ UTF-16 code units of the characters that the lexers look for.
3
+ */
4
+ export const TAB = 0x09;
5
+ export const LF = 0x0A;
6
+ export const SPACE = 0x20;
7
+ export const DOUBLE_QUOTE = 0x22;
8
+ export const HASH = 0x23;
9
+ export const SINGLE_QUOTE = 0x27;
10
+ export const ASTERISK = 0x2A;
11
+ export const COMMA = 0x2C;
12
+ export const DASH = 0x2D;
13
+ export const DOT = 0x2E;
14
+ export const SLASH = 0x2F;
15
+ export const COLON = 0x3A;
16
+ export const OPEN_BRACKET = 0x5B;
17
+ export const BACKSLASH = 0x5C;
18
+ export const CLOSE_BRACKET = 0x5D;
19
+ export const OPEN_BRACE = 0x7B;
20
+ export const CLOSE_BRACE = 0x7D;
21
+ /*
22
+ The deepest nesting either direction accepts. A recursive-descent parser would otherwise end in an engine stack overflow.
23
+ */
24
+ export const MAX_DEPTH = 100;
25
+ export const INT64_MIN = -(2n ** 63n);
26
+ export const INT64_MAX = (2n ** 63n) - 1n;
27
+ /*
28
+ The instant range is checked in UTC, so that every instant has a canonical form that can be read back.
29
+ */
30
+ export const MIN_INSTANT = -62135596800000000000n; // 0001-01-01T00:00:00Z
31
+ export const MAX_INSTANT = 253402300799999999999n; // 9999-12-31T23:59:59.999999999Z
32
+ /*
33
+ `Temporal` is built into Node.js 26 and later. Before that, only the values of instants and durations need it, so that everything else works without a polyfill, including `format()` and `parseTree()`. It is read at import, so a polyfill must be loaded before this package.
34
+ */
35
+ const temporal = typeof Temporal === 'undefined' ? undefined : Temporal;
36
+ export function requireTemporal() {
37
+ if (temporal === undefined) {
38
+ throw new Error('Instants and durations need `Temporal`. Use Node.js 26 or later, or load a polyfill such as `temporal-polyfill/global` before this package');
39
+ }
40
+ return temporal;
41
+ }
42
+ /*
43
+ The duration units in their required order, with their length in nanoseconds.
44
+ */
45
+ export const DURATION_UNITS = new Map([
46
+ ['h', 3600000000000n],
47
+ ['m', 60000000000n],
48
+ ['s', 1000000000n],
49
+ ['ms', 1000000n],
50
+ ['us', 1000n],
51
+ ['ns', 1n],
52
+ ]);
53
+ /**
54
+ Remove the trailing zeros of a run of digits. A loop rather than a regular expression, which would be quadratic on a long run of zeros followed by another digit.
55
+ */
56
+ export function trimTrailingZeros(digits) {
57
+ let end = digits.length;
58
+ while (end > 0 && digits.charCodeAt(end - 1) === 0x30) {
59
+ end--;
60
+ }
61
+ return digits.slice(0, end);
62
+ }
63
+ /**
64
+ A duration as a `Temporal.Duration` in hours and smaller units, which hold any int64 count of nanoseconds exactly.
65
+ */
66
+ export function createDuration(nanoseconds) {
67
+ const sign = nanoseconds < 0n ? -1n : 1n;
68
+ let rest = nanoseconds * sign;
69
+ const parts = [];
70
+ for (const size of DURATION_UNITS.values()) {
71
+ parts.push(Number(sign * (rest / size)));
72
+ rest %= size;
73
+ }
74
+ const [hours, minutes, seconds, milliseconds, microseconds, nanosecondsPart] = parts;
75
+ return new (requireTemporal().Duration)(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanosecondsPart);
76
+ }
77
+ /**
78
+ The `integers` option of `parse()`, `stringify()`, and `edit()`, checked, or its default when it is left out.
79
+ */
80
+ export function getIntegersOption(options) {
81
+ if (options === undefined) {
82
+ return 'bigint';
83
+ }
84
+ if (typeof options !== 'object' || options === null) {
85
+ throw new TypeError(`The options must be an object, got ${options === null ? 'null' : typeof options}`);
86
+ }
87
+ const { integers = 'bigint' } = options;
88
+ if (integers !== 'bigint' && integers !== 'number') {
89
+ throw new TypeError(`The \`integers\` option must be 'bigint' or 'number', got ${typeof integers === 'string' ? `'${abbreviate(integers)}'` : typeof integers}`);
90
+ }
91
+ return integers;
92
+ }
93
+ /*
94
+ Names for the characters that are invisible or easy to mistake, so an error can say what it found.
95
+ */
96
+ const CHARACTER_NAMES = new Map([
97
+ [0x06_1C, 'ARABIC LETTER MARK'],
98
+ [0x85, 'NEXT LINE'],
99
+ [0xA0, 'NO-BREAK SPACE'],
100
+ [0xAD, 'SOFT HYPHEN'],
101
+ [0x16_80, 'OGHAM SPACE MARK'],
102
+ [0x20_00, 'EN QUAD'],
103
+ [0x20_01, 'EM QUAD'],
104
+ [0x20_02, 'EN SPACE'],
105
+ [0x20_03, 'EM SPACE'],
106
+ [0x20_04, 'THREE-PER-EM SPACE'],
107
+ [0x20_05, 'FOUR-PER-EM SPACE'],
108
+ [0x20_06, 'SIX-PER-EM SPACE'],
109
+ [0x20_07, 'FIGURE SPACE'],
110
+ [0x20_08, 'PUNCTUATION SPACE'],
111
+ [0x20_09, 'THIN SPACE'],
112
+ [0x20_0A, 'HAIR SPACE'],
113
+ [0x20_0B, 'ZERO WIDTH SPACE'],
114
+ [0x20_0C, 'ZERO WIDTH NON-JOINER'],
115
+ [0x20_0D, 'ZERO WIDTH JOINER'],
116
+ [0x20_0E, 'LEFT-TO-RIGHT MARK'],
117
+ [0x20_0F, 'RIGHT-TO-LEFT MARK'],
118
+ [0x20_28, 'LINE SEPARATOR'],
119
+ [0x20_29, 'PARAGRAPH SEPARATOR'],
120
+ [0x20_2A, 'LEFT-TO-RIGHT EMBEDDING'],
121
+ [0x20_2B, 'RIGHT-TO-LEFT EMBEDDING'],
122
+ [0x20_2C, 'POP DIRECTIONAL FORMATTING'],
123
+ [0x20_2D, 'LEFT-TO-RIGHT OVERRIDE'],
124
+ [0x20_2E, 'RIGHT-TO-LEFT OVERRIDE'],
125
+ [0x20_2F, 'NARROW NO-BREAK SPACE'],
126
+ [0x20_5F, 'MEDIUM MATHEMATICAL SPACE'],
127
+ [0x20_60, 'WORD JOINER'],
128
+ [0x20_66, 'LEFT-TO-RIGHT ISOLATE'],
129
+ [0x20_67, 'RIGHT-TO-LEFT ISOLATE'],
130
+ [0x20_68, 'FIRST STRONG ISOLATE'],
131
+ [0x20_69, 'POP DIRECTIONAL ISOLATE'],
132
+ [0x30_00, 'IDEOGRAPHIC SPACE'],
133
+ [0xFE_FF, 'ZERO WIDTH NO-BREAK SPACE'],
134
+ ]);
135
+ /*
136
+ Letters, digits, `_`, and `-`, the characters of a bare key, as a lookup table by code unit.
137
+ */
138
+ const bareKeyCharacters = new Uint8Array(128);
139
+ for (const character of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-') {
140
+ bareKeyCharacters[character.charCodeAt(0)] = 1;
141
+ }
142
+ /*
143
+ A number, an instant, or a duration is lexed as one maximal run of these characters, and then validated as a whole. The validation is a hand-written scan rather than a regular expression, because a regular expression with a repeated group runs out of stack on a token of a few million characters.
144
+ */
145
+ const numberCharacters = new Uint8Array(128);
146
+ for (const character of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.:+-') {
147
+ numberCharacters[character.charCodeAt(0)] = 1;
148
+ }
149
+ /**
150
+ Whether a UTF-16 code unit can be part of a bare key. `NaN`, which `charCodeAt()` returns past the end of a string, cannot.
151
+ */
152
+ export function isBareKeyCharacter(code) {
153
+ return code < 128 && bareKeyCharacters[code] === 1;
154
+ }
155
+ /**
156
+ Whether a key can be written without quotes, as a bare key: one or more ASCII letters, digits, `_`, or `-`, in any order, so `404` and `-x` are bare keys too. `stringify()` writes such a key bare and quotes every other key.
157
+
158
+ @param key - The decoded key.
159
+ @returns Whether `key` can be a bare key.
160
+
161
+ @example
162
+ ```
163
+ import {isBareKey} from 'soml-lang';
164
+
165
+ isBareKey('content-type');
166
+ //=> true
167
+
168
+ isBareKey('a.b');
169
+ //=> false
170
+ ```
171
+ */
172
+ export function isBareKey(key) {
173
+ if (key === '') {
174
+ return false;
175
+ }
176
+ for (let index = 0; index < key.length; index++) {
177
+ if (!isBareKeyCharacter(key.charCodeAt(index))) {
178
+ return false;
179
+ }
180
+ }
181
+ return true;
182
+ }
183
+ /**
184
+ A key as an error message shows it: bare when it can be, and otherwise quoted, and cut short when it is long.
185
+ */
186
+ export function describeKey(key) {
187
+ return isBareKey(key) ? abbreviate(key) : JSON.stringify(abbreviate(key));
188
+ }
189
+ /**
190
+ The end of the number, instant, or duration token that starts at `start`, which is a digit, `-`, or the `i` of `infinity`.
191
+ */
192
+ export function findNumberEnd(source, start) {
193
+ let end = start + 1;
194
+ while (isNumberCharacter(source.charCodeAt(end))) {
195
+ end++;
196
+ }
197
+ return end;
198
+ }
199
+ function isNumberCharacter(code) {
200
+ return code < 128 && numberCharacters[code] === 1;
201
+ }
202
+ /**
203
+ The offset of the line feed that ends the line containing `offset`, or the length of `source` on the last line.
204
+ */
205
+ export function findLineEnd(source, offset) {
206
+ const end = source.indexOf('\n', offset);
207
+ return end === -1 ? source.length : end;
208
+ }
209
+ /**
210
+ Find the closing delimiter of a block string: the first line from `offset` on that holds, after zero or more spaces and tabs, a run of exactly `length` of the `quote` character. A longer run is content. Returns where that line and its delimiter start, or `undefined` when no line closes the block.
211
+ */
212
+ export function findBlockStringEnd(source, offset, quote, length) {
213
+ for (let lineStart = offset; lineStart < source.length; lineStart = findLineEnd(source, lineStart) + 1) {
214
+ const delimiterStart = skipSpaces(source, lineStart);
215
+ let runLength = 0;
216
+ while (source.charCodeAt(delimiterStart + runLength) === quote) {
217
+ runLength++;
218
+ }
219
+ if (runLength === length) {
220
+ return { lineStart, delimiterStart };
221
+ }
222
+ }
223
+ // Past the last line, either because the source ended with a line break or because the last line was content.
224
+ return undefined;
225
+ }
226
+ /**
227
+ Whether a UTF-16 code unit is a space or a tab, the whitespace within a line.
228
+ */
229
+ export function isSpace(code) {
230
+ return code === SPACE || code === TAB;
231
+ }
232
+ /*
233
+ Whitespace within a line is scanned with loops rather than regular expressions such as `/[\t ]*:/`, because a regular expression that backtracks over a run of a few million spaces runs out of stack.
234
+ */
235
+ /**
236
+ The index after the spaces and tabs from `index` on.
237
+ */
238
+ export function skipSpaces(text, index) {
239
+ while (isSpace(text.charCodeAt(index))) {
240
+ index++;
241
+ }
242
+ return index;
243
+ }
244
+ /**
245
+ The index after the last character before `index` that is not a space or a tab.
246
+ */
247
+ export function skipSpacesBack(text, index) {
248
+ while (index > 0 && isSpace(text.charCodeAt(index - 1))) {
249
+ index--;
250
+ }
251
+ return index;
252
+ }
253
+ /**
254
+ Whether a line in a block string is blank: empty, or only spaces and tabs. A blank line becomes an empty line in the value.
255
+ */
256
+ export function isBlankLine(line) {
257
+ return skipSpaces(line, 0) === line.length;
258
+ }
259
+ /**
260
+ Shorten text quoted in an error message, so a huge token does not make a huge message. The cut never splits a surrogate pair.
261
+ */
262
+ export function abbreviate(text, maximumLength = 40) {
263
+ if (text.length <= maximumLength) {
264
+ return text;
265
+ }
266
+ const lastCode = text.charCodeAt(maximumLength - 1);
267
+ const end = lastCode >= 0xD8_00 && lastCode <= 0xDB_FF ? maximumLength - 1 : maximumLength;
268
+ return `${text.slice(0, end)}…`;
269
+ }
270
+ export function formatCodePoint(codePoint) {
271
+ return `U+${codePoint.toString(16).toUpperCase().padStart(4, '0')}`;
272
+ }
273
+ /**
274
+ Describe a character for an error message, such as `“$”` or `U+00A0 (NO-BREAK SPACE)`.
275
+ */
276
+ export function describeCharacter(codePoint) {
277
+ const name = CHARACTER_NAMES.get(codePoint);
278
+ if (name !== undefined) {
279
+ const isWhitespace = /^[\p{White_Space}\u{200B}\u{FEFF}]$/v.test(String.fromCodePoint(codePoint));
280
+ return `${formatCodePoint(codePoint)} (${name}${isWhitespace ? '; only space, tab, and line feed are whitespace' : ''})`;
281
+ }
282
+ const character = String.fromCodePoint(codePoint);
283
+ // Control, format, private-use, unassigned, surrogate, separator, and default-ignorable characters are invisible or ambiguous, so they are shown by code point.
284
+ return codePoint !== 0x20 && /^[\p{Default_Ignorable_Code_Point}\p{Other}\p{Separator}]$/v.test(character) ? formatCodePoint(codePoint) : `“${character}”`;
285
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ Options for `stringify()`.
3
+ */
4
+ export type StringifyOptions = {
5
+ /**
6
+ How an int is represented in `value`.
7
+
8
+ - `'bigint'`: A `bigint` is written as an int, and a `number` is always written as a float, so `8080` becomes `8080.0`.
9
+ - `'number'`: A `number` that is a safe integer is written as an int, and any other `number` as a float. A `bigint` is still written as an int.
10
+
11
+ @default 'bigint'
12
+ */
13
+ readonly integers?: 'bigint' | 'number';
14
+ /**
15
+ Write exact canonical form, with the members of every object sorted by key, for hashing, signing, or comparing.
16
+
17
+ By default, members keep the order of `value`, which reads better in a file for people, and every other rule of canonical form is followed. JavaScript puts integer-like keys, such as `404` or `10`, before every other key, in numeric order, so their order in `value` cannot be kept.
18
+
19
+ @default false
20
+
21
+ @example
22
+ ```
23
+ import {stringify} from 'soml-lang';
24
+
25
+ stringify({name: 'api', description: 'The edge service'});
26
+ //=> "name: 'api'\ndescription: 'The edge service'\n"
27
+
28
+ stringify({name: 'api', description: 'The edge service'}, {canonical: true});
29
+ //=> "description: 'The edge service'\nname: 'api'\n"
30
+ ```
31
+ */
32
+ readonly canonical?: boolean;
33
+ };
34
+ /**
35
+ Serialize an object or an array to SOML. Nesting is written with braces and tabs, and the output ends with one line feed. Comments, dotted keys, and block strings are never written.
36
+
37
+ Members keep the order of `value`, which reads better in a file for people, and every other rule of canonical form is followed. With the `canonical: true` option, members are sorted by key, and the output is canonical form: two equal values produce the same bytes, so it can be hashed, signed, or compared.
38
+
39
+ JavaScript puts integer-like keys, such as `404` or `10`, before every other key, in numeric order, so their order in `value` cannot be kept.
40
+
41
+ A `bigint` is written as an int and a `number` as a float, so `{port: 8080}` becomes `port: 8080.0`. Use `8080n`, or the `integers: 'number'` option.
42
+
43
+ Besides the types that `parse()` returns, a `Date` is accepted and written as an instant, a `Temporal.Duration` is accepted when it has no years, months, weeks, or days, because those are not a fixed length, and a member whose value is `undefined` is left out, as `JSON.stringify` does.
44
+
45
+ @param value - A plain object or an array.
46
+ @param options - How an int is represented in `value`, and whether to sort members.
47
+ @returns The document, ending with one line feed.
48
+ @throws {TypeError} For a value that cannot be represented: `NaN`, a function, a symbol value, an object that is neither a plain object nor an array (such as a class instance or a `Map`), `undefined` in an array, a circular reference, a non-collection at the top level, an invalid `Date`, a `Temporal.Duration` with years, months, weeks, or days, an object that only claims to be a `Date`, a `Temporal.Instant`, or a `Temporal.Duration`, and a string or key with a lone surrogate or a carriage return. Also when `options` is not an object, its `integers` is not `'bigint'` or `'number'`, or its `canonical` is not a boolean.
49
+ @throws {RangeError} For an int outside the 64-bit range, an instant outside the years 0001 to 9999, a duration outside the 64-bit range of nanoseconds, and nesting deeper than 100 levels.
50
+
51
+ @example
52
+ ```
53
+ import {stringify} from 'soml-lang';
54
+
55
+ stringify({name: 'api-gateway', replicas: 3n, timeout: 30});
56
+ //=> "name: 'api-gateway'\nreplicas: 3\ntimeout: 30.0\n"
57
+
58
+ stringify({port: 8080}, {integers: 'number'});
59
+ //=> 'port: 8080\n'
60
+
61
+ stringify({name: 'api', description: 'The edge service'}, {canonical: true});
62
+ //=> "description: 'The edge service'\nname: 'api'\n"
63
+ ```
64
+ */
65
+ export declare function stringify(value: object, options?: StringifyOptions): string;
66
+ /**
67
+ Serialize one value as it is written in a document: a scalar on one line, and an object or an array in braces or brackets over several lines, with each level indented by one more tab. There is no line feed at the end.
68
+
69
+ For showing a value, as in a message, or for writing it into a template. Use `stringify()` to write a whole document, and `edit()` to change a value in one.
70
+
71
+ It takes the same values and options as `stringify()`, and any of them at the top level, so `1.5` gives `'1.5'` and `'it\'s'` gives `"it's"`. Members keep their order, as in `stringify()`, unless the `canonical: true` option sorts them.
72
+
73
+ @param value - A value of any type that `stringify()` accepts in a document.
74
+ @param options - How an int is represented in `value`, and whether to sort members.
75
+ @returns The value as it is written in a document.
76
+ @throws {TypeError} For a value that cannot be represented, as for `stringify()`, and for `undefined`.
77
+ @throws {RangeError} For a value out of range, as for `stringify()`.
78
+
79
+ @example
80
+ ```
81
+ import {stringifyValue} from 'soml-lang';
82
+
83
+ stringifyValue(0.1 + 0.2);
84
+ //=> '0.30000000000000004'
85
+
86
+ stringifyValue(8080n);
87
+ //=> '8080'
88
+
89
+ stringifyValue({b: 1n, a: [true]});
90
+ //=> '{\n\tb: 1\n\ta: [\n\t\ttrue\n\t]\n}'
91
+
92
+ stringifyValue({b: 1n, a: [true]}, {canonical: true});
93
+ //=> '{\n\ta: [\n\t\ttrue\n\t]\n\tb: 1\n}'
94
+ ```
95
+ */
96
+ export declare function stringifyValue(value: unknown, options?: StringifyOptions): string;
97
+ /**
98
+ Compare two keys in canonical order, which is by their Unicode scalar values, as `stringify()` sorts members with the `canonical: true` option. For a sort, such as in a lint rule that keeps the members of an object in canonical order.
99
+
100
+ The default `Array#sort()` compares UTF-16 code units, which puts U+E000 to U+FFFF after every character above U+FFFF, so it differs from canonical order for keys that hold such characters.
101
+
102
+ @param left - A key.
103
+ @param right - Another key.
104
+ @returns A negative number when `left` comes first, a positive number when `right` comes first, and `0` when they are equal.
105
+
106
+ @example
107
+ ```
108
+ import {compareKeys} from 'soml-lang';
109
+
110
+ ['b', 'a', '😀', 'ff'].toSorted(compareKeys);
111
+ //=> ['a', 'b', 'ff', '😀']
112
+ ```
113
+ */
114
+ export declare function compareKeys(left: string, right: string): number;
115
+ export declare function formatKey(key: string): string;