@idfkit/core 0.2.0-rc.2 → 0.3.0-rc.1
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/dist/conformance.d.ts +1 -1
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +1 -1
- package/dist/conformance.js.map +1 -1
- package/dist/document.d.ts +16 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +0 -0
- package/dist/document.js.map +1 -1
- package/dist/extensible.d.ts +5 -0
- package/dist/extensible.d.ts.map +1 -0
- package/dist/extensible.js +126 -0
- package/dist/extensible.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +11 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +11 -0
- package/dist/internal.js.map +1 -1
- package/dist/introspect/describe.d.ts +1 -12
- package/dist/introspect/describe.d.ts.map +1 -1
- package/dist/introspect/describe.js +16 -0
- package/dist/introspect/describe.js.map +1 -1
- package/dist/object.d.ts +34 -2
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +43 -6
- package/dist/object.js.map +1 -1
- package/dist/parse/epjson.d.ts.map +1 -1
- package/dist/parse/epjson.js +23 -1
- package/dist/parse/epjson.js.map +1 -1
- package/dist/parse/idf.d.ts +16 -0
- package/dist/parse/idf.d.ts.map +1 -1
- package/dist/parse/idf.js +102 -50
- package/dist/parse/idf.js.map +1 -1
- package/dist/parse/lexer.d.ts +6 -13
- package/dist/parse/lexer.d.ts.map +1 -1
- package/dist/parse/lexer.js +63 -110
- package/dist/parse/lexer.js.map +1 -1
- package/dist/parse/scan.d.ts +34 -0
- package/dist/parse/scan.d.ts.map +1 -0
- package/dist/parse/scan.js +177 -0
- package/dist/parse/scan.js.map +1 -0
- package/dist/preserve/source.d.ts +3 -0
- package/dist/preserve/source.d.ts.map +1 -0
- package/dist/preserve/source.js +60 -0
- package/dist/preserve/source.js.map +1 -0
- package/dist/preserve/write.d.ts +2 -0
- package/dist/preserve/write.d.ts.map +1 -0
- package/dist/preserve/write.js +85 -0
- package/dist/preserve/write.js.map +1 -0
- package/dist/syntax/classify.d.ts +25 -0
- package/dist/syntax/classify.d.ts.map +1 -0
- package/dist/syntax/classify.js +59 -0
- package/dist/syntax/classify.js.map +1 -0
- package/dist/syntax/layer.d.ts +67 -0
- package/dist/syntax/layer.d.ts.map +1 -0
- package/dist/syntax/layer.js +158 -0
- package/dist/syntax/layer.js.map +1 -0
- package/dist/syntax/region.d.ts +73 -0
- package/dist/syntax/region.d.ts.map +1 -0
- package/dist/syntax/region.js +105 -0
- package/dist/syntax/region.js.map +1 -0
- package/dist/syntax/tokens.d.ts +80 -0
- package/dist/syntax/tokens.d.ts.map +1 -0
- package/dist/syntax/tokens.js +140 -0
- package/dist/syntax/tokens.js.map +1 -0
- package/dist/write/epjson.d.ts +19 -0
- package/dist/write/epjson.d.ts.map +1 -1
- package/dist/write/epjson.js +12 -0
- package/dist/write/epjson.js.map +1 -1
- package/dist/write/idf.d.ts +23 -7
- package/dist/write/idf.d.ts.map +1 -1
- package/dist/write/idf.js +51 -7
- package/dist/write/idf.js.map +1 -1
- package/package.json +4 -4
package/dist/parse/lexer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lexer.js","sourceRoot":"","sources":["../../src/parse/lexer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lexer.js","sourceRoot":"","sources":["../../src/parse/lexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IDF character rules, in one place.
|
|
3
|
+
*
|
|
4
|
+
* The grammar is small:
|
|
5
|
+
* - `!` starts a comment running to the end of its line
|
|
6
|
+
* - `,` separates fields
|
|
7
|
+
* - `;` terminates a statement
|
|
8
|
+
* - everything else is field text
|
|
9
|
+
*
|
|
10
|
+
* There are no string literals and no escape sequences, so a comma cannot occur inside a field
|
|
11
|
+
* value and an `!` always starts a comment. Real files depend on both.
|
|
12
|
+
*
|
|
13
|
+
* A hand-written character walk rather than a regex. The Python library matches objects with a
|
|
14
|
+
* `(?:[^;!]*(?:![^\n]*\n)?)*?` inner loop; that is a nested quantifier, so it backtracks badly on
|
|
15
|
+
* malformed input and cannot report where the problem was. A scan is about the same amount of
|
|
16
|
+
* code, is linear in the input, and always knows its line number.
|
|
17
|
+
*
|
|
18
|
+
* **Why this file exists at all.** Every position this library reports depends on the syntax layer
|
|
19
|
+
* and the model-building read agreeing about where a field begins and ends. Two implementations of
|
|
20
|
+
* "step over a comment between a separator and its value" that differ by one character put a
|
|
21
|
+
* finding on the wrong field, and that class of bug stays invisible until a file puts a comment
|
|
22
|
+
* somewhere unusual. The rules used to exist twice, once inside `lex` and once inside the
|
|
23
|
+
* `fieldLine` helper of `idf.ts`; both are now callers of this scan (research R3).
|
|
24
|
+
*
|
|
25
|
+
* **This module is internal and stays internal.** It is not exported from the package root. The
|
|
26
|
+
* syntax layer, which is public, is what a consumer outside the package reaches for; keeping the
|
|
27
|
+
* scan itself unexported is the reason the layer ships in core rather than beside the language
|
|
28
|
+
* service, because a package boundary drawn around both halves would have forced this file into a
|
|
29
|
+
* published export purely so the other half could reach it.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/parse/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IDF character rules, in one place.
|
|
3
|
+
*
|
|
4
|
+
* The grammar is small:
|
|
5
|
+
* - `!` starts a comment running to the end of its line
|
|
6
|
+
* - `,` separates fields
|
|
7
|
+
* - `;` terminates a statement
|
|
8
|
+
* - everything else is field text
|
|
9
|
+
*
|
|
10
|
+
* There are no string literals and no escape sequences, so a comma cannot occur inside a field
|
|
11
|
+
* value and an `!` always starts a comment. Real files depend on both.
|
|
12
|
+
*
|
|
13
|
+
* A hand-written character walk rather than a regex. The Python library matches objects with a
|
|
14
|
+
* `(?:[^;!]*(?:![^\n]*\n)?)*?` inner loop; that is a nested quantifier, so it backtracks badly on
|
|
15
|
+
* malformed input and cannot report where the problem was. A scan is about the same amount of
|
|
16
|
+
* code, is linear in the input, and always knows its line number.
|
|
17
|
+
*
|
|
18
|
+
* **Why this file exists at all.** Every position this library reports depends on the syntax layer
|
|
19
|
+
* and the model-building read agreeing about where a field begins and ends. Two implementations of
|
|
20
|
+
* "step over a comment between a separator and its value" that differ by one character put a
|
|
21
|
+
* finding on the wrong field, and that class of bug stays invisible until a file puts a comment
|
|
22
|
+
* somewhere unusual. The rules used to exist twice, once inside `lex` and once inside the
|
|
23
|
+
* `fieldLine` helper of `idf.ts`; both are now callers of this scan (research R3).
|
|
24
|
+
*
|
|
25
|
+
* **This module is internal and stays internal.** It is not exported from the package root. The
|
|
26
|
+
* syntax layer, which is public, is what a consumer outside the package reaches for; keeping the
|
|
27
|
+
* scan itself unexported is the reason the layer ships in core rather than beside the language
|
|
28
|
+
* service, because a package boundary drawn around both halves would have forced this file into a
|
|
29
|
+
* published export purely so the other half could reach it.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
const EXCLAMATION = 0x21;
|
|
34
|
+
const COMMA = 0x2c;
|
|
35
|
+
const SEMICOLON = 0x3b;
|
|
36
|
+
const LINE_FEED = 0x0a;
|
|
37
|
+
/**
|
|
38
|
+
* Walk IDF text, reporting what the handler asked for.
|
|
39
|
+
*
|
|
40
|
+
* One linear pass, no allocation of its own, and it never throws: text that violates the grammar
|
|
41
|
+
* is reported as what it is rather than stopped at.
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export function scan(text, handler, options = {}) {
|
|
46
|
+
const length = text.length;
|
|
47
|
+
let index = options.from ?? 0;
|
|
48
|
+
let line = options.line ?? 1;
|
|
49
|
+
/** Offset of the current line's first character, which is what turns an offset into a column. */
|
|
50
|
+
let lineStart = index - ((options.column ?? 1) - 1);
|
|
51
|
+
/** Start of the current field's text run, moved past every comment that interrupts it. */
|
|
52
|
+
let fieldStart = index;
|
|
53
|
+
/** 0 for the type name, 1 for the first field after it. */
|
|
54
|
+
let fieldIndex = 0;
|
|
55
|
+
/** True once the current statement's first non-blank, non-comment character has been seen. */
|
|
56
|
+
let open = false;
|
|
57
|
+
/** First non-blank character of the current field's value, or -1 while it has none. */
|
|
58
|
+
let valueStart = -1;
|
|
59
|
+
/** One past the last non-blank character of the run `valueStart` falls in. */
|
|
60
|
+
let valueEnd = -1;
|
|
61
|
+
/** Line `valueStart` falls on. */
|
|
62
|
+
let valueLine = 1;
|
|
63
|
+
/** True once a comment has ended the run holding `valueStart`. */
|
|
64
|
+
let valueClosed = false;
|
|
65
|
+
/**
|
|
66
|
+
* Report the field the scan is inside, closed at `at`. False when a handler asked to stop.
|
|
67
|
+
*
|
|
68
|
+
* A field with no value text is positioned at `at`, which is the separator, the terminator, or
|
|
69
|
+
* the end of input that closed it. That is the offset a value would have begun at, because
|
|
70
|
+
* everything between the field's start and `at` was whitespace or comment.
|
|
71
|
+
*/
|
|
72
|
+
const closeField = (at) => {
|
|
73
|
+
if (at > fieldStart && handler.fieldText?.(fieldStart, at) === false)
|
|
74
|
+
return false;
|
|
75
|
+
const empty = valueStart < 0;
|
|
76
|
+
const stop = handler.fieldEnd?.(fieldIndex, empty ? at : valueStart, empty ? at : valueEnd, empty ? line : valueLine) === false;
|
|
77
|
+
valueStart = -1;
|
|
78
|
+
valueEnd = -1;
|
|
79
|
+
valueClosed = false;
|
|
80
|
+
return !stop;
|
|
81
|
+
};
|
|
82
|
+
while (index < length) {
|
|
83
|
+
const code = text.charCodeAt(index);
|
|
84
|
+
if (code === EXCLAMATION) {
|
|
85
|
+
// Text seen before the comment still belongs to the field, and so does text after the line
|
|
86
|
+
// feed: the comment interrupts the field without ending it. This is what lets
|
|
87
|
+
// `Zone1, !- Name` work, where the comment is no part of the value and the value is no part
|
|
88
|
+
// of the comment.
|
|
89
|
+
if (index > fieldStart && handler.fieldText?.(fieldStart, index) === false)
|
|
90
|
+
return;
|
|
91
|
+
const feed = text.indexOf('\n', index);
|
|
92
|
+
const end = feed === -1 ? length : feed;
|
|
93
|
+
if (handler.comment?.(index, end) === false)
|
|
94
|
+
return;
|
|
95
|
+
if (valueStart >= 0)
|
|
96
|
+
valueClosed = true;
|
|
97
|
+
if (feed === -1) {
|
|
98
|
+
fieldStart = length;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
index = feed + 1;
|
|
102
|
+
fieldStart = index;
|
|
103
|
+
line += 1;
|
|
104
|
+
lineStart = index;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const blank = isSpace(code);
|
|
108
|
+
if (!open && !blank) {
|
|
109
|
+
open = true;
|
|
110
|
+
if (handler.statementStart?.(index, line, index - lineStart + 1) === false)
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (code === COMMA || code === SEMICOLON) {
|
|
114
|
+
if (!closeField(index))
|
|
115
|
+
return;
|
|
116
|
+
if (code === COMMA) {
|
|
117
|
+
if (handler.separator?.(index) === false)
|
|
118
|
+
return;
|
|
119
|
+
fieldIndex += 1;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
if (handler.terminator?.(index) === false)
|
|
123
|
+
return;
|
|
124
|
+
if (handler.statementEnd?.(index + 1, false) === false)
|
|
125
|
+
return;
|
|
126
|
+
fieldIndex = 0;
|
|
127
|
+
open = false;
|
|
128
|
+
}
|
|
129
|
+
index += 1;
|
|
130
|
+
fieldStart = index;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (code === LINE_FEED) {
|
|
134
|
+
line += 1;
|
|
135
|
+
lineStart = index + 1;
|
|
136
|
+
}
|
|
137
|
+
else if (!blank) {
|
|
138
|
+
if (valueStart < 0) {
|
|
139
|
+
valueStart = index;
|
|
140
|
+
valueEnd = index + 1;
|
|
141
|
+
valueLine = line;
|
|
142
|
+
}
|
|
143
|
+
else if (!valueClosed) {
|
|
144
|
+
valueEnd = index + 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
index += 1;
|
|
148
|
+
}
|
|
149
|
+
// Input ran out inside a statement. Its last field is reported like any other, so a caller reads
|
|
150
|
+
// what was written rather than having to reconstruct it from the leftovers.
|
|
151
|
+
if (!open)
|
|
152
|
+
return;
|
|
153
|
+
if (!closeField(length))
|
|
154
|
+
return;
|
|
155
|
+
handler.statementEnd?.(length, true);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether a character is whitespace, by the same definition `String.prototype.trim` uses.
|
|
159
|
+
*
|
|
160
|
+
* A field's value is trimmed, so a scan that disagreed with `trim` about one character would put
|
|
161
|
+
* a region beside the value rather than on it. ASCII is one comparison; the rest of the set costs
|
|
162
|
+
* a branch nothing outside a comment ever takes.
|
|
163
|
+
*/
|
|
164
|
+
function isSpace(code) {
|
|
165
|
+
if (code < 0x80)
|
|
166
|
+
return code === 0x20 || (code >= 0x09 && code <= 0x0d);
|
|
167
|
+
return (code === 0xa0 ||
|
|
168
|
+
code === 0x1680 ||
|
|
169
|
+
(code >= 0x2000 && code <= 0x200a) ||
|
|
170
|
+
code === 0x2028 ||
|
|
171
|
+
code === 0x2029 ||
|
|
172
|
+
code === 0x202f ||
|
|
173
|
+
code === 0x205f ||
|
|
174
|
+
code === 0x3000 ||
|
|
175
|
+
code === 0xfeff);
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/parse/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/preserve/source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AA8E9C,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,SAAS,GAAG,OAAO,CAKxF"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SOURCE } from '../internal.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether an object is still exactly the characters it was read from.
|
|
4
|
+
*
|
|
5
|
+
* The third clause is not redundant: an object carrying an index from a file it is no longer in
|
|
6
|
+
* fails the identity check, which is what makes a document assembled from more than one source
|
|
7
|
+
* fall out rather than need handling.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export function isWholeDocumentUntouched(document, source) {
|
|
12
|
+
if (document.size !== source.countAtRead)
|
|
13
|
+
return false;
|
|
14
|
+
for (const obj of document.objects()) {
|
|
15
|
+
if (!isUntouched(obj, source))
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
export function isUntouched(obj, source) {
|
|
21
|
+
if (source === undefined)
|
|
22
|
+
return false;
|
|
23
|
+
const at = obj[SOURCE];
|
|
24
|
+
if (at === undefined)
|
|
25
|
+
return false;
|
|
26
|
+
return source.anchors[at] === obj;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The statement each raw object was read from, by index into `objects`.
|
|
30
|
+
*
|
|
31
|
+
* The lexer records one offset per object and the layer records the same offset as the start of
|
|
32
|
+
* that statement's region, so the two match on a number both already hold. One pass with two
|
|
33
|
+
* cursors: the lexer's sequence is a subsequence of the layer's, because some statements produce
|
|
34
|
+
* no object.
|
|
35
|
+
*
|
|
36
|
+
* An object matching no statement gets `undefined` and is written by formatting, which is the
|
|
37
|
+
* honest answer for one whose characters cannot be located.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export function statementIndexes(layer, objects) {
|
|
42
|
+
const found = new Array(objects.length).fill(undefined);
|
|
43
|
+
const statements = layer.statements;
|
|
44
|
+
let at = 0;
|
|
45
|
+
for (let index = 0; index < objects.length; index += 1) {
|
|
46
|
+
const offset = objects[index]?.offset;
|
|
47
|
+
if (offset === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
while (at < statements.length && statements[at].region.start < offset)
|
|
50
|
+
at += 1;
|
|
51
|
+
if (at >= statements.length)
|
|
52
|
+
break;
|
|
53
|
+
if (statements[at].region.start === offset) {
|
|
54
|
+
found[index] = at;
|
|
55
|
+
at += 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/preserve/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AA2DxC;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAA8D,EAC9D,MAAuB;IAEvB,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IACvD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAc,EAAE,MAAmC;IAC7E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAkB,EAClB,OAA6B;IAE7B,MAAM,KAAK,GAA2B,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACpC,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,OAAO,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,EAAE,CAAE,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM;YAAE,EAAE,IAAI,CAAC,CAAC;QAChF,IAAI,EAAE,IAAI,UAAU,CAAC,MAAM;YAAE,MAAM;QACnC,IAAI,UAAU,CAAC,EAAE,CAAE,CAAC,MAAM,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YAC5C,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YAClB,EAAE,IAAI,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/preserve/write.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { OWNER } from '../internal.js';
|
|
2
|
+
import { writeObject } from '../write/idf.js';
|
|
3
|
+
import { isUntouched } from './source.js';
|
|
4
|
+
/**
|
|
5
|
+
* Reproduce the text a document was read from, per object.
|
|
6
|
+
*
|
|
7
|
+
* A walk over the statements the read scanned and the gaps between them. The gaps are
|
|
8
|
+
* unconditional and only the statements are decided: every character is either inside a statement
|
|
9
|
+
* or in a gap, because the layer tiles the text, and nothing in a gap belongs to an object. That
|
|
10
|
+
* is what makes the one-object diff structural rather than careful, and it is why nothing is
|
|
11
|
+
* reordered, no header is added and the version statement is not moved.
|
|
12
|
+
*
|
|
13
|
+
* A statement's region ends at its terminator, so a comment trailing the semicolon on the same
|
|
14
|
+
* line is in the gap: removing an object leaves it, and reformatting one leaves it below the new
|
|
15
|
+
* text. Behaviour rather than defects, and the alternative is a writer that guesses which comments
|
|
16
|
+
* are about which object.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function writePreserved(document, source, options) {
|
|
21
|
+
const text = source.layer.text;
|
|
22
|
+
const statements = source.layer.statements;
|
|
23
|
+
const parts = [];
|
|
24
|
+
// Everything before the first statement, which for a file with none is the whole text: an empty
|
|
25
|
+
// file and a file of comments are both reproduced by this line alone.
|
|
26
|
+
parts.push(text.slice(0, statements[0]?.region.start ?? text.length));
|
|
27
|
+
for (let index = 0; index < statements.length; index += 1) {
|
|
28
|
+
const statement = statements[index];
|
|
29
|
+
parts.push(statementPart(source, index, text, options));
|
|
30
|
+
// Unconditional: the gap is emitted whether the statement was copied, reformatted or dropped.
|
|
31
|
+
parts.push(text.slice(statement.region.end, statements[index + 1]?.region.start ?? text.length));
|
|
32
|
+
}
|
|
33
|
+
appendNewObjects(document, source, parts, options);
|
|
34
|
+
return parts.join('');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* An anchor that is `undefined` is a statement the read rejected, an unknown type or a duplicate
|
|
38
|
+
* name. Its characters are reproduced: the read already reported a diagnostic, and the write is
|
|
39
|
+
* not the place to delete text the author wrote.
|
|
40
|
+
*/
|
|
41
|
+
function statementPart(source, index, text, options) {
|
|
42
|
+
const statement = source.layer.statements[index];
|
|
43
|
+
const verbatim = text.slice(statement.region.start, statement.region.end);
|
|
44
|
+
const anchored = source.anchors[index];
|
|
45
|
+
if (anchored === undefined)
|
|
46
|
+
return verbatim;
|
|
47
|
+
// Removal is answered from ownership: `remove` already clears the owner, and recording it on
|
|
48
|
+
// the object would mean holding a reference to something the document has let go.
|
|
49
|
+
if (anchored[OWNER] === undefined)
|
|
50
|
+
return '';
|
|
51
|
+
if (isUntouched(anchored, source))
|
|
52
|
+
return verbatim;
|
|
53
|
+
return writeObject(anchored, options);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Objects no anchor names, formatted and appended after everything the layer holds.
|
|
57
|
+
*
|
|
58
|
+
* The end is the only placement that cannot disturb text the author wrote. The newline guard is
|
|
59
|
+
* the file-with-no-trailing-newline case: appending must not run onto the author's last line.
|
|
60
|
+
*/
|
|
61
|
+
function appendNewObjects(document, source, parts, options) {
|
|
62
|
+
const anchored = new Set(source.anchors);
|
|
63
|
+
// The last non-empty part rather than a join of everything so far, which would make appending N
|
|
64
|
+
// objects cost N passes over the whole file.
|
|
65
|
+
let tail = lastNonEmpty(parts);
|
|
66
|
+
for (const obj of document.objects()) {
|
|
67
|
+
if (anchored.has(obj))
|
|
68
|
+
continue;
|
|
69
|
+
if (tail !== '' && !tail.endsWith('\n'))
|
|
70
|
+
parts.push('\n');
|
|
71
|
+
parts.push(writeObject(obj, options));
|
|
72
|
+
parts.push('\n');
|
|
73
|
+
tail = '\n';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** The last part that carries a character, which is what decides whether the output ends in one. */
|
|
77
|
+
function lastNonEmpty(parts) {
|
|
78
|
+
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
79
|
+
const part = parts[index];
|
|
80
|
+
if (part !== '')
|
|
81
|
+
return part;
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/preserve/write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SyntaxLayer } from './layer.js';
|
|
2
|
+
import type { Token } from './tokens.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every character of the text, as tokens, in source order.
|
|
5
|
+
*
|
|
6
|
+
* Two things happen here that the layer deliberately does not pay to store.
|
|
7
|
+
*
|
|
8
|
+
* The first is `trivia`. Whitespace is the complement of the stored tokens, so storing it would
|
|
9
|
+
* roughly double the token count to hold something derivable by subtraction. This function fills
|
|
10
|
+
* each gap as it walks, which is what makes the layer's complete coverage observable (FR-016)
|
|
11
|
+
* without the layer carrying it: the sequence begins at offset 0, ends at `text.length`, and has
|
|
12
|
+
* no gap and no overlap anywhere between.
|
|
13
|
+
*
|
|
14
|
+
* The second is the line split. No yielded token crosses a line boundary (FR-047), while a stored
|
|
15
|
+
* `value` region may, because the format lets a field be written across two lines and real files
|
|
16
|
+
* do it. Every token encoding in use, the Language Server Protocol's included, expresses a token
|
|
17
|
+
* as a length on a single line, so a token spanning one cannot be encoded at all. The split adds
|
|
18
|
+
* tokens and moves no boundary, so the tiling holds exactly as before, and the stored region stays
|
|
19
|
+
* whole for everything that is not being drawn.
|
|
20
|
+
*
|
|
21
|
+
* A generator, so a consumer colouring a viewport stops where it stops rather than materialising
|
|
22
|
+
* four hundred thousand tokens to read the first fifty.
|
|
23
|
+
*/
|
|
24
|
+
export declare function classify(layer: SyntaxLayer): Iterable<Token>;
|
|
25
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/syntax/classify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAiB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAkC7D"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { lineStartsOf } from './region.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every character of the text, as tokens, in source order.
|
|
4
|
+
*
|
|
5
|
+
* Two things happen here that the layer deliberately does not pay to store.
|
|
6
|
+
*
|
|
7
|
+
* The first is `trivia`. Whitespace is the complement of the stored tokens, so storing it would
|
|
8
|
+
* roughly double the token count to hold something derivable by subtraction. This function fills
|
|
9
|
+
* each gap as it walks, which is what makes the layer's complete coverage observable (FR-016)
|
|
10
|
+
* without the layer carrying it: the sequence begins at offset 0, ends at `text.length`, and has
|
|
11
|
+
* no gap and no overlap anywhere between.
|
|
12
|
+
*
|
|
13
|
+
* The second is the line split. No yielded token crosses a line boundary (FR-047), while a stored
|
|
14
|
+
* `value` region may, because the format lets a field be written across two lines and real files
|
|
15
|
+
* do it. Every token encoding in use, the Language Server Protocol's included, expresses a token
|
|
16
|
+
* as a length on a single line, so a token spanning one cannot be encoded at all. The split adds
|
|
17
|
+
* tokens and moves no boundary, so the tiling holds exactly as before, and the stored region stays
|
|
18
|
+
* whole for everything that is not being drawn.
|
|
19
|
+
*
|
|
20
|
+
* A generator, so a consumer colouring a viewport stops where it stops rather than materialising
|
|
21
|
+
* four hundred thousand tokens to read the first fifty.
|
|
22
|
+
*/
|
|
23
|
+
export function* classify(layer) {
|
|
24
|
+
const text = layer.text;
|
|
25
|
+
const tokens = layer.tokens;
|
|
26
|
+
const starts = tokens.starts;
|
|
27
|
+
const ends = tokens.ends;
|
|
28
|
+
// The layer's own line index, built once and shared with every position query made against it.
|
|
29
|
+
const lineStarts = lineStartsOf(layer);
|
|
30
|
+
/** The line the walk is on. It only ever moves forward, so the whole pass stays linear. */
|
|
31
|
+
let line = 0;
|
|
32
|
+
/** One token per line the span touches, split at each line start strictly inside it. */
|
|
33
|
+
function* perLine(from, to, kind) {
|
|
34
|
+
while (line + 1 < lineStarts.length && lineStarts[line + 1] <= from)
|
|
35
|
+
line += 1;
|
|
36
|
+
let at = from;
|
|
37
|
+
while (line + 1 < lineStarts.length && lineStarts[line + 1] < to) {
|
|
38
|
+
line += 1;
|
|
39
|
+
const boundary = lineStarts[line];
|
|
40
|
+
yield { start: at, end: boundary, kind };
|
|
41
|
+
at = boundary;
|
|
42
|
+
}
|
|
43
|
+
if (at < to)
|
|
44
|
+
yield { start: at, end: to, kind };
|
|
45
|
+
}
|
|
46
|
+
/** One past the last character yielded so far, which is where the next gap would begin. */
|
|
47
|
+
let covered = 0;
|
|
48
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
49
|
+
const start = starts[index];
|
|
50
|
+
if (start > covered)
|
|
51
|
+
yield* perLine(covered, start, 'trivia');
|
|
52
|
+
const end = ends[index];
|
|
53
|
+
yield* perLine(start, end, tokens.kindAt(index));
|
|
54
|
+
covered = end;
|
|
55
|
+
}
|
|
56
|
+
if (covered < text.length)
|
|
57
|
+
yield* perLine(covered, text.length, 'trivia');
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/syntax/classify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,SAAS,CAAC,CAAC,QAAQ,CAAC,KAAkB;IAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,+FAA+F;IAC/F,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAEvC,2FAA2F;IAC3F,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,wFAAwF;IACxF,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAY,EAAE,EAAU,EAAE,IAAe;QACzD,OAAO,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,IAAI,GAAG,CAAC,CAAE,IAAI,IAAI;YAAE,IAAI,IAAI,CAAC,CAAC;QAChF,IAAI,EAAE,GAAG,IAAI,CAAC;QACd,OAAO,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,IAAI,GAAG,CAAC,CAAE,GAAG,EAAE,EAAE,CAAC;YAClE,IAAI,IAAI,CAAC,CAAC;YACV,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAE,CAAC;YACnC,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzC,EAAE,GAAG,QAAQ,CAAC;QAChB,CAAC;QACD,IAAI,EAAE,GAAG,EAAE;YAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,2FAA2F;IAC3F,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAE,CAAC;QAC7B,IAAI,KAAK,GAAG,OAAO;YAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAE,CAAC;QACzB,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,OAAO,GAAG,GAAG,CAAC;IAChB,CAAC;IACD,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM;QAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Region } from './region.js';
|
|
2
|
+
import { TokenStore } from './tokens.js';
|
|
3
|
+
/**
|
|
4
|
+
* One statement as the text writes it, terminated or not.
|
|
5
|
+
*
|
|
6
|
+
* Not an object in the model and carrying no schema meaning (FR-006): a statement whose type the
|
|
7
|
+
* schema never heard of, or which carries twice the fields its type defines, is still a statement.
|
|
8
|
+
* Deciding what any of it means is the reader's job, and the layer is what a reader positions
|
|
9
|
+
* against.
|
|
10
|
+
*/
|
|
11
|
+
export interface Statement {
|
|
12
|
+
/** The whole statement, from its first non-blank character through its terminator. */
|
|
13
|
+
readonly region: Region;
|
|
14
|
+
/** Just the type name. Empty, at the offset one would have begun, when none was written. */
|
|
15
|
+
readonly typeName: Region;
|
|
16
|
+
/** Type name text, exactly as written and not case-folded. */
|
|
17
|
+
readonly typeNameText: string;
|
|
18
|
+
/**
|
|
19
|
+
* Fields after the type name, in positional order, one region each.
|
|
20
|
+
*
|
|
21
|
+
* Index 0 is the first field after the type name, matching `RawObject.values`, which has already
|
|
22
|
+
* had the type name shifted off. A field written empty still gets a region, an empty one
|
|
23
|
+
* positioned where its value would have begun, so that positional indexing never shifts and a
|
|
24
|
+
* blank slot in the middle of an extensible group can still be pointed at.
|
|
25
|
+
*
|
|
26
|
+
* The count is what was written rather than what the type defines. A statement carrying more or
|
|
27
|
+
* fewer fields than its type is a finding, not a representation problem.
|
|
28
|
+
*/
|
|
29
|
+
readonly fields: readonly Region[];
|
|
30
|
+
/** True when no terminator was found, meaning the statement runs to end of input. */
|
|
31
|
+
readonly unterminated: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Everything the text contains and where it was.
|
|
35
|
+
*
|
|
36
|
+
* The layer holds the text it was built from, which is what makes byte-identical reconstruction a
|
|
37
|
+
* consequence rather than a feature: a reconstruction defined as concatenating slices of that text
|
|
38
|
+
* returns the text by construction. What can actually break is the tiling, so that is what the
|
|
39
|
+
* corpus test asserts and what {@link classify} makes observable.
|
|
40
|
+
*
|
|
41
|
+
* Nothing builds a layer implicitly (FR-005). `lex` and `parseIdf` read the same characters
|
|
42
|
+
* through the same scan and construct none of this, so a caller who never names `scanIdf` pays
|
|
43
|
+
* neither its time nor its memory.
|
|
44
|
+
*/
|
|
45
|
+
export interface SyntaxLayer {
|
|
46
|
+
/** The text this layer was built from. Held so regions can be resolved. */
|
|
47
|
+
readonly text: string;
|
|
48
|
+
/** Statements in source order. */
|
|
49
|
+
readonly statements: readonly Statement[];
|
|
50
|
+
/** Every meaningful token in source order, packed. Whitespace is not among them. */
|
|
51
|
+
readonly tokens: TokenStore;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Scan IDF text into a syntax layer.
|
|
55
|
+
*
|
|
56
|
+
* Text and nothing else: no schema, because the layer records what the text says and never what it
|
|
57
|
+
* means (FR-006). It never throws, for any input, and text that violates the grammar is
|
|
58
|
+
* represented rather than stopped at (FR-004): an unterminated final statement runs to end of
|
|
59
|
+
* input and says so, a statement written without a type name still tiles, and empty text produces
|
|
60
|
+
* a layer with no statements and no tokens, which satisfies the tiling invariant vacuously.
|
|
61
|
+
*
|
|
62
|
+
* One linear pass over the shared scan, which is the same pass `lex` makes. The budget is a
|
|
63
|
+
* quarter over a plain read of the same text; the work above the scan is a push per token and a
|
|
64
|
+
* record per statement.
|
|
65
|
+
*/
|
|
66
|
+
export declare function scanIdf(text: string): SyntaxLayer;
|
|
67
|
+
//# sourceMappingURL=layer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer.d.ts","sourceRoot":"","sources":["../../src/syntax/layer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,qFAAqF;IACrF,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAIjD"}
|