@idfkit/core 0.2.0-rc.1 → 0.2.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/dist/conformance.d.ts +1 -1
- package/dist/conformance.js +1 -1
- 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/introspect/describe.d.ts +2 -2
- package/dist/introspect/describe.d.ts.map +1 -1
- package/dist/introspect/describe.js +62 -8
- package/dist/introspect/describe.js.map +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +22 -2
- package/dist/node.js.map +1 -1
- package/dist/parse/idf.d.ts +21 -3
- package/dist/parse/idf.d.ts.map +1 -1
- package/dist/parse/idf.js +158 -5
- package/dist/parse/idf.js.map +1 -1
- package/dist/parse/lexer.d.ts +42 -13
- package/dist/parse/lexer.d.ts.map +1 -1
- package/dist/parse/lexer.js +65 -83
- 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/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 +134 -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/idf.d.ts +32 -0
- package/dist/write/idf.d.ts.map +1 -1
- package/dist/write/idf.js +24 -5
- package/dist/write/idf.js.map +1 -1
- package/package.json +4 -4
package/dist/parse/lexer.d.ts
CHANGED
|
@@ -6,11 +6,47 @@ export interface RawObject {
|
|
|
6
6
|
values: string[];
|
|
7
7
|
/** 1-based line where the object starts, for diagnostics. */
|
|
8
8
|
line: number;
|
|
9
|
+
/** 1-based column where the object starts, for diagnostics. */
|
|
10
|
+
column?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Absolute offset of the object's first character in the source text.
|
|
13
|
+
*
|
|
14
|
+
* One number per object, so that a finding about a FIELD can be positioned without the lexer
|
|
15
|
+
* recording a line for every field of every object. The rescan that uses it runs only when a
|
|
16
|
+
* finding is being built.
|
|
17
|
+
*/
|
|
18
|
+
offset?: number;
|
|
9
19
|
}
|
|
10
20
|
export interface LexDiagnostic {
|
|
11
21
|
message: string;
|
|
12
22
|
line: number;
|
|
23
|
+
/**
|
|
24
|
+
* Machine-readable kind, from the vocabulary both libraries share.
|
|
25
|
+
*
|
|
26
|
+
* Derived from Python's exception hierarchy by dropping the `Error` suffix, so neither language
|
|
27
|
+
* invented it and the mapping stays mechanical. The conformance corpus compares a finding on
|
|
28
|
+
* `(code, line, typeName)` and never on `message`: wording is a presentation choice each library
|
|
29
|
+
* should stay free to improve, and pinning it would turn every improvement into a failure.
|
|
30
|
+
*/
|
|
31
|
+
code?: ParseDiagnosticCode;
|
|
32
|
+
/** 1-based column, when the lexer knew one. */
|
|
33
|
+
column?: number;
|
|
34
|
+
/** Path the text came from, when it came from a file rather than a string. */
|
|
35
|
+
filepath?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Object type the problem occurred in, when known.
|
|
38
|
+
*
|
|
39
|
+
* Declared here rather than only on `ParseDiagnostic` because the lexer knows it too: an
|
|
40
|
+
* unterminated object has read its type name before it runs out of input, and a finding that
|
|
41
|
+
* drops it says only that something went wrong somewhere.
|
|
42
|
+
*/
|
|
43
|
+
typeName?: string;
|
|
13
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The shared diagnostic vocabulary. The table lives in
|
|
47
|
+
* `idfkit-conformance/runners/compare.md`; a code outside it is a difference, not a near match.
|
|
48
|
+
*/
|
|
49
|
+
export type ParseDiagnosticCode = 'UnknownObjectType' | 'InvalidField' | 'Range' | 'DuplicateObject' | 'ParseError' | 'VersionMismatch' | 'UnsupportedVersion' | 'SchemaNotFound';
|
|
14
50
|
export interface LexOptions {
|
|
15
51
|
/** Report a problem instead of throwing. */
|
|
16
52
|
onDiagnostic?: (diagnostic: LexDiagnostic) => void;
|
|
@@ -18,20 +54,13 @@ export interface LexOptions {
|
|
|
18
54
|
/**
|
|
19
55
|
* Split IDF text into raw objects.
|
|
20
56
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the problem was. A scanner is about the same amount of code, is linear in the
|
|
25
|
-
* input, and always knows its line number.
|
|
26
|
-
*
|
|
27
|
-
* The grammar is small:
|
|
28
|
-
* - `!` starts a comment running to end of line
|
|
29
|
-
* - `,` separates fields
|
|
30
|
-
* - `;` terminates an object
|
|
31
|
-
* - everything else is field text, trimmed
|
|
57
|
+
* The character rules are not here: they live in `scan.ts`, which the syntax layer reads through
|
|
58
|
+
* as well. Two copies of "step over a comment between a separator and its value" that differ by
|
|
59
|
+
* one character put a finding on the wrong field, so there is one copy (research R3).
|
|
32
60
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
61
|
+
* This function is what remains once those rules are elsewhere: assembling values from the text
|
|
62
|
+
* runs the scan reports, and turning a statement into a `RawObject` or into a diagnostic. It asks
|
|
63
|
+
* for no comment and no region, so it pays for neither, and it builds no syntax layer.
|
|
35
64
|
*/
|
|
36
65
|
export declare function lex(text: string, options?: LexOptions): RawObject[];
|
|
37
66
|
//# sourceMappingURL=lexer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/parse/lexer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/parse/lexer.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAC3B,mBAAmB,GACnB,cAAc,GACd,OAAO,GACP,iBAAiB,GACjB,YAAY,GACZ,iBAAiB,GACjB,oBAAoB,GACpB,gBAAgB,CAAC;AAErB,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,aAAa,KAAK,IAAI,CAAC;CACpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,SAAS,EAAE,CAgFvE"}
|
package/dist/parse/lexer.js
CHANGED
|
@@ -1,105 +1,87 @@
|
|
|
1
|
+
import { scan } from './scan.js';
|
|
1
2
|
/**
|
|
2
3
|
* Split IDF text into raw objects.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the problem was. A scanner is about the same amount of code, is linear in the
|
|
8
|
-
* input, and always knows its line number.
|
|
5
|
+
* The character rules are not here: they live in `scan.ts`, which the syntax layer reads through
|
|
6
|
+
* as well. Two copies of "step over a comment between a separator and its value" that differ by
|
|
7
|
+
* one character put a finding on the wrong field, so there is one copy (research R3).
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - `;` terminates an object
|
|
14
|
-
* - everything else is field text, trimmed
|
|
15
|
-
*
|
|
16
|
-
* There are no string literals and no escape sequences, so a comma cannot occur
|
|
17
|
-
* inside a field value. Real files depend on that.
|
|
9
|
+
* This function is what remains once those rules are elsewhere: assembling values from the text
|
|
10
|
+
* runs the scan reports, and turning a statement into a `RawObject` or into a diagnostic. It asks
|
|
11
|
+
* for no comment and no region, so it pays for neither, and it builds no syntax layer.
|
|
18
12
|
*/
|
|
19
13
|
export function lex(text, options = {}) {
|
|
20
14
|
const objects = [];
|
|
21
15
|
const report = options.onDiagnostic;
|
|
22
|
-
|
|
23
|
-
/** Field text pieces, split whenever a comment interrupts a field. */
|
|
16
|
+
/** Field text pieces, one per run the scan reports, joined when the field closes. */
|
|
24
17
|
let chunks = [];
|
|
25
18
|
/** Fields of the object being read; index 0 ends up being the type name. */
|
|
26
19
|
let values = [];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Where the current statement starts.
|
|
22
|
+
*
|
|
23
|
+
* The first NON-BLANK character, not the start of the field text: Python's regex matches the
|
|
24
|
+
* type name itself, so an object indented three spaces reports column 4 there and has to report
|
|
25
|
+
* column 4 here too, or the corpus compares two different notions of position.
|
|
26
|
+
*/
|
|
30
27
|
let objectLine = 1;
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
chunks
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
28
|
+
let objectColumn = 0;
|
|
29
|
+
let objectOffset = -1;
|
|
30
|
+
scan(text, {
|
|
31
|
+
statementStart(offset, line, column) {
|
|
32
|
+
objectLine = line;
|
|
33
|
+
objectColumn = column;
|
|
34
|
+
objectOffset = offset;
|
|
35
|
+
},
|
|
36
|
+
fieldText(start, end) {
|
|
37
|
+
chunks.push(text.slice(start, end));
|
|
38
|
+
},
|
|
39
|
+
fieldEnd() {
|
|
40
|
+
values.push(chunks.join('').trim());
|
|
41
|
+
chunks = [];
|
|
42
|
+
},
|
|
43
|
+
statementEnd(_end, unterminated) {
|
|
44
|
+
if (unterminated) {
|
|
45
|
+
// The scan closes the field the input ran out inside, so the trailing text arrived as the
|
|
46
|
+
// last value rather than as a leftover. Popping it leaves `values` holding exactly what a
|
|
47
|
+
// terminated statement would have held, which is what the message reads from.
|
|
48
|
+
const trailing = values.pop() ?? '';
|
|
49
|
+
if (trailing !== '' || values.length > 0) {
|
|
50
|
+
report?.({
|
|
51
|
+
message: `Unterminated object near "${trailing.slice(0, 40) || values[0]}" (missing ";")`,
|
|
52
|
+
line: objectLine,
|
|
53
|
+
code: 'ParseError',
|
|
54
|
+
column: objectColumn || undefined,
|
|
55
|
+
// `values` has not been shifted, because the shift happens on `;` and there was none,
|
|
56
|
+
// so the type name is still at the front. Reporting it is what lets the corpus compare
|
|
57
|
+
// this finding against Python's on `(code, line, typeName)`.
|
|
58
|
+
typeName: values[0],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
values = [];
|
|
62
|
+
return;
|
|
57
63
|
}
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (char === ',') {
|
|
61
|
-
values.push(endField(index));
|
|
62
|
-
objectStarted = true;
|
|
63
|
-
index += 1;
|
|
64
|
-
fieldStart = index;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (char === ';') {
|
|
68
|
-
values.push(endField(index));
|
|
69
|
-
index += 1;
|
|
70
|
-
fieldStart = index;
|
|
71
64
|
const typeName = values.shift() ?? '';
|
|
72
65
|
if (typeName === '') {
|
|
73
|
-
report?.({
|
|
66
|
+
report?.({
|
|
67
|
+
message: 'Object with no type name',
|
|
68
|
+
line: objectLine,
|
|
69
|
+
column: objectColumn || undefined,
|
|
70
|
+
code: 'ParseError',
|
|
71
|
+
});
|
|
74
72
|
}
|
|
75
73
|
else {
|
|
76
|
-
objects.push({
|
|
74
|
+
objects.push({
|
|
75
|
+
typeName,
|
|
76
|
+
values,
|
|
77
|
+
line: objectLine,
|
|
78
|
+
column: objectColumn || undefined,
|
|
79
|
+
offset: objectOffset >= 0 ? objectOffset : undefined,
|
|
80
|
+
});
|
|
77
81
|
}
|
|
78
82
|
values = [];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (char === '\n') {
|
|
84
|
-
line += 1;
|
|
85
|
-
if (!objectStarted &&
|
|
86
|
-
chunks.join('').trim() === '' &&
|
|
87
|
-
text.slice(fieldStart, index).trim() === '') {
|
|
88
|
-
// Blank line before any object content: keep the start line current.
|
|
89
|
-
chunks = [];
|
|
90
|
-
fieldStart = index + 1;
|
|
91
|
-
objectLine = line;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
index += 1;
|
|
95
|
-
}
|
|
96
|
-
const trailing = (chunks.join('') + text.slice(fieldStart, length)).trim();
|
|
97
|
-
if (trailing !== '' || values.length > 0) {
|
|
98
|
-
report?.({
|
|
99
|
-
message: `Unterminated object near "${trailing.slice(0, 40) || values[0]}" (missing ";")`,
|
|
100
|
-
line: objectLine,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
83
|
+
},
|
|
84
|
+
});
|
|
103
85
|
return objects;
|
|
104
86
|
}
|
|
105
87
|
//# sourceMappingURL=lexer.js.map
|
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":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmEjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,UAAsB,EAAE;IACxD,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAEpC,qFAAqF;IACrF,IAAI,MAAM,GAAa,EAAE,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,MAAM,GAAa,EAAE,CAAC;IAE1B;;;;;;OAMG;IACH,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC;IAEtB,IAAI,CAAC,IAAI,EAAE;QACT,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM;YACjC,UAAU,GAAG,IAAI,CAAC;YAClB,YAAY,GAAG,MAAM,CAAC;YACtB,YAAY,GAAG,MAAM,CAAC;QACxB,CAAC;QAED,SAAS,CAAC,KAAK,EAAE,GAAG;YAClB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QACtC,CAAC;QAED,QAAQ;YACN,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACpC,MAAM,GAAG,EAAE,CAAC;QACd,CAAC;QAED,YAAY,CAAC,IAAI,EAAE,YAAY;YAC7B,IAAI,YAAY,EAAE,CAAC;gBACjB,0FAA0F;gBAC1F,0FAA0F;gBAC1F,8EAA8E;gBAC9E,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;gBACpC,IAAI,QAAQ,KAAK,EAAE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACzC,MAAM,EAAE,CAAC;wBACP,OAAO,EAAE,6BAA6B,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,iBAAiB;wBACzF,IAAI,EAAE,UAAU;wBAChB,IAAI,EAAE,YAAY;wBAClB,MAAM,EAAE,YAAY,IAAI,SAAS;wBACjC,sFAAsF;wBACtF,uFAAuF;wBACvF,6DAA6D;wBAC7D,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;qBACpB,CAAC,CAAC;gBACL,CAAC;gBACD,MAAM,GAAG,EAAE,CAAC;gBACZ,OAAO;YACT,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;YACtC,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;gBACpB,MAAM,EAAE,CAAC;oBACP,OAAO,EAAE,0BAA0B;oBACnC,IAAI,EAAE,UAAU;oBAChB,MAAM,EAAE,YAAY,IAAI,SAAS;oBACjC,IAAI,EAAE,YAAY;iBACnB,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC;oBACX,QAAQ;oBACR,MAAM;oBACN,IAAI,EAAE,UAAU;oBAChB,MAAM,EAAE,YAAY,IAAI,SAAS;oBACjC,MAAM,EAAE,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;iBACrD,CAAC,CAAC;YACL,CAAC;YACD,MAAM,GAAG,EAAE,CAAC;QACd,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -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,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"}
|