links-notation 0.22.0 → 0.24.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/README.md +3 -0
- package/dist/index.js +904 -109
- package/index.d.ts +96 -0
- package/package.json +4 -4
- package/src/Binary.js +772 -0
- package/src/Link.js +3 -47
- package/src/ReferenceLiteral.js +66 -0
- package/src/grammar.pegjs +12 -2
- package/src/index.js +16 -0
- package/src/parser-generated.js +262 -89
- package/tests/Binary.test.js +165 -0
- package/tests/EmptyReference.test.js +2 -2
- package/tests/ReferenceFidelity.test.js +99 -0
- package/bun.lock +0 -213
package/src/Link.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatReference } from './ReferenceLiteral.js';
|
|
2
|
+
|
|
1
3
|
export class Link {
|
|
2
4
|
/**
|
|
3
5
|
* Create a new Link
|
|
@@ -86,53 +88,7 @@ export class Link {
|
|
|
86
88
|
return '';
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
// back as itself instead of disappearing from the document.
|
|
91
|
-
if (reference === '') {
|
|
92
|
-
return '""';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const hasSingleQuote = reference.includes("'");
|
|
96
|
-
const hasDoubleQuote = reference.includes('"');
|
|
97
|
-
|
|
98
|
-
// A reference that begins with a `#` has to be quoted, or it would read
|
|
99
|
-
// back as a comment. A `#` anywhere else in a reference is content
|
|
100
|
-
// (`issue#1047`), so only the first character matters.
|
|
101
|
-
const needsQuoting =
|
|
102
|
-
reference.startsWith('#') ||
|
|
103
|
-
reference.includes(':') ||
|
|
104
|
-
reference.includes('(') ||
|
|
105
|
-
reference.includes(')') ||
|
|
106
|
-
reference.includes(' ') ||
|
|
107
|
-
reference.includes('\t') ||
|
|
108
|
-
reference.includes('\n') ||
|
|
109
|
-
reference.includes('\r') ||
|
|
110
|
-
hasDoubleQuote ||
|
|
111
|
-
hasSingleQuote;
|
|
112
|
-
|
|
113
|
-
// Handle edge case: reference contains both single and double quotes
|
|
114
|
-
if (hasSingleQuote && hasDoubleQuote) {
|
|
115
|
-
// Escape single quotes and wrap in single quotes
|
|
116
|
-
return `'${reference.replace(/'/g, "\\'")}'`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Prefer single quotes if double quotes are present
|
|
120
|
-
if (hasDoubleQuote) {
|
|
121
|
-
return `'${reference}'`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Use double quotes if single quotes are present
|
|
125
|
-
if (hasSingleQuote) {
|
|
126
|
-
return `"${reference}"`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Use single quotes for special characters
|
|
130
|
-
if (needsQuoting) {
|
|
131
|
-
return `'${reference}'`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// No quoting needed
|
|
135
|
-
return reference;
|
|
91
|
+
return formatReference(reference);
|
|
136
92
|
}
|
|
137
93
|
|
|
138
94
|
/**
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* global TextEncoder, TextDecoder */
|
|
2
|
+
|
|
3
|
+
/** Detect the reserved versioned-literal prefix at a reference boundary. */
|
|
4
|
+
export function hasReferenceLiteralPrefix(text, start = 0) {
|
|
5
|
+
return /^~[0-9]+\{/.test(text.slice(start));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Encode exact Unicode text as a version 1 UTF-8 hexadecimal reference. */
|
|
9
|
+
export function encodeReferenceLiteral(text) {
|
|
10
|
+
if (typeof text !== 'string' || !text.isWellFormed()) {
|
|
11
|
+
throw new TypeError('Reference must be well-formed Unicode text');
|
|
12
|
+
}
|
|
13
|
+
const hex = Array.from(new TextEncoder().encode(text), (byte) =>
|
|
14
|
+
byte.toString(16).padStart(2, '0')
|
|
15
|
+
).join('');
|
|
16
|
+
return `~1{${hex}}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Decode a complete versioned literal, rejecting malformed bytes and versions. */
|
|
20
|
+
export function decodeReferenceLiteral(literal) {
|
|
21
|
+
const match = /^~([0-9]+)\{([0-9a-fA-F]*)\}$/.exec(literal);
|
|
22
|
+
if (
|
|
23
|
+
!match ||
|
|
24
|
+
match[0] !== literal ||
|
|
25
|
+
match[1] !== '1' ||
|
|
26
|
+
match[2].length % 2 !== 0
|
|
27
|
+
) {
|
|
28
|
+
throw new TypeError(
|
|
29
|
+
'Invalid or unsupported reference literal (expected ~1{UTF-8 hex})'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(
|
|
33
|
+
Uint8Array.from(match[2].match(/../g) ?? [], (hex) => parseInt(hex, 16))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Use readable legacy quoting where possible and a literal for empty/control text. */
|
|
38
|
+
export function formatReference(text) {
|
|
39
|
+
if (typeof text !== 'string' || !text.isWellFormed()) {
|
|
40
|
+
throw new TypeError('Reference must be well-formed Unicode text');
|
|
41
|
+
}
|
|
42
|
+
if (!text || /[\u0000-\u001f\u007f]/u.test(text)) {
|
|
43
|
+
return encodeReferenceLiteral(text);
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
!text.startsWith('#') &&
|
|
47
|
+
!/^~[0-9]+\{/.test(text) &&
|
|
48
|
+
!/[\p{White_Space}\ufeff():"'`]/u.test(text)
|
|
49
|
+
) {
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
let choice = null;
|
|
53
|
+
for (const quote of ["'", '"', '`']) {
|
|
54
|
+
if (text.startsWith(quote)) continue;
|
|
55
|
+
let longest = 0;
|
|
56
|
+
let run = 0;
|
|
57
|
+
for (const character of text) {
|
|
58
|
+
run = character === quote ? run + 1 : 0;
|
|
59
|
+
longest = Math.max(longest, run);
|
|
60
|
+
}
|
|
61
|
+
const count = (longest + 1) | 1;
|
|
62
|
+
if (choice === null || count < choice.count) choice = { quote, count };
|
|
63
|
+
}
|
|
64
|
+
const delimiter = choice.quote.repeat(choice.count);
|
|
65
|
+
return delimiter + text + delimiter;
|
|
66
|
+
}
|
package/src/grammar.pegjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{{
|
|
2
2
|
import { DelimitedReferences } from './quotes.js';
|
|
3
|
+
import { decodeReferenceLiteral, hasReferenceLiteralPrefix } from './ReferenceLiteral.js';
|
|
3
4
|
}}
|
|
4
5
|
|
|
5
6
|
{
|
|
@@ -217,9 +218,18 @@ indentedIdLink = id:reference __ ":" eol { return { id: id, values: [] }; }
|
|
|
217
218
|
|
|
218
219
|
// Reference can be quoted (with any number of quotes N >= 1) or simple unquoted
|
|
219
220
|
// Universal approach: use procedural parsing for all quote types and counts
|
|
220
|
-
reference = quotedReference / simpleReference
|
|
221
|
+
reference = referenceLiteral / quotedReference / simpleReference
|
|
222
|
+
|
|
223
|
+
// Versioned literals reserve ~<version>{<UTF-8 hex>} at a reference boundary.
|
|
224
|
+
// A malformed reserved token must fail rather than fall back to a bare name.
|
|
225
|
+
referenceLiteral = &'~' &{
|
|
226
|
+
return hasReferenceLiteralPrefix(input, offset());
|
|
227
|
+
} literal:$('~' [0-9]+ '{' [^} \t\r\n()]* '}') {
|
|
228
|
+
return decodeReferenceLiteral(literal);
|
|
229
|
+
}
|
|
221
230
|
|
|
222
|
-
simpleReference =
|
|
231
|
+
simpleReference = &{ return !hasReferenceLiteralPrefix(input, offset()); }
|
|
232
|
+
chars:referenceSymbol+ { return chars.join(''); }
|
|
223
233
|
|
|
224
234
|
// Universal quoted reference - handles any N quotes for all quote types
|
|
225
235
|
// Uses procedural parsing with input/offset() for clean, simple logic
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { Link, formatLinks } from './Link.js';
|
|
2
|
+
export {
|
|
3
|
+
encodeReferenceLiteral,
|
|
4
|
+
decodeReferenceLiteral,
|
|
5
|
+
} from './ReferenceLiteral.js';
|
|
2
6
|
export { LinksGroup } from './LinksGroup.js';
|
|
3
7
|
export { Parser, DEFAULT_MAX_DEPTH } from './Parser.js';
|
|
4
8
|
export { StreamParseError, StreamParser } from './StreamParser.js';
|
|
@@ -6,3 +10,15 @@ export { ParseError } from './ParseError.js';
|
|
|
6
10
|
export { FormatConfig } from './FormatConfig.js';
|
|
7
11
|
export { FormatOptions } from './FormatOptions.js';
|
|
8
12
|
export { stripComments } from './comments.js';
|
|
13
|
+
export {
|
|
14
|
+
ArityRange,
|
|
15
|
+
BinaryLinoCodec,
|
|
16
|
+
BinaryLinoOptions,
|
|
17
|
+
DecodeLimits,
|
|
18
|
+
External,
|
|
19
|
+
LinksPacket,
|
|
20
|
+
PacketReader,
|
|
21
|
+
Section,
|
|
22
|
+
formatBinaryDocument,
|
|
23
|
+
formatBinaryReference,
|
|
24
|
+
} from './Binary.js';
|