@effected/toml 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/Toml.js +165 -0
- package/TomlDateTime.js +147 -0
- package/TomlDiagnostic.js +106 -0
- package/TomlDocument.js +133 -0
- package/TomlEdit.js +52 -0
- package/TomlFormat.js +611 -0
- package/TomlNode.js +212 -0
- package/TomlVisitor.js +152 -0
- package/index.d.ts +897 -0
- package/index.js +10 -0
- package/internal/diagnostics.js +53 -0
- package/internal/limits.js +24 -0
- package/internal/parser.js +408 -0
- package/internal/scanner.js +493 -0
- package/internal/semantic.js +253 -0
- package/internal/stringifyValue.js +191 -0
- package/package.json +45 -0
- package/tsdoc-metadata.json +11 -0
package/TomlNode.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlOffsetDateTime } from "./TomlDateTime.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/TomlNode.ts
|
|
5
|
+
/**
|
|
6
|
+
* The three simple-key spellings: `bare`, `basic` (`"..."`) and `literal`
|
|
7
|
+
* (`'...'`).
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
const TomlKeyKind = Schema.Literals([
|
|
12
|
+
"bare",
|
|
13
|
+
"basic",
|
|
14
|
+
"literal"
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* One simple key within a (possibly dotted) key path.
|
|
18
|
+
*
|
|
19
|
+
* - `value` — the decoded key text (escapes resolved, quotes stripped).
|
|
20
|
+
* - `kind` — how the key was spelled in the source.
|
|
21
|
+
* - `offset` / `length` — the key's span in the source, quotes included.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
var TomlKey = class extends Schema.TaggedClass()("TomlKey", {
|
|
26
|
+
value: Schema.String,
|
|
27
|
+
kind: TomlKeyKind,
|
|
28
|
+
offset: Schema.Number,
|
|
29
|
+
length: Schema.Number
|
|
30
|
+
}) {};
|
|
31
|
+
/**
|
|
32
|
+
* The four TOML string forms.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
const TomlStringStyle = Schema.Literals([
|
|
37
|
+
"basic",
|
|
38
|
+
"literal",
|
|
39
|
+
"multiline-basic",
|
|
40
|
+
"multiline-literal"
|
|
41
|
+
]);
|
|
42
|
+
/**
|
|
43
|
+
* A string value node. `value` is the decoded text; the raw spelling lives in
|
|
44
|
+
* the source span.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
var TomlString = class extends Schema.TaggedClass()("TomlString", {
|
|
49
|
+
value: Schema.String,
|
|
50
|
+
style: TomlStringStyle,
|
|
51
|
+
offset: Schema.Number,
|
|
52
|
+
length: Schema.Number
|
|
53
|
+
}) {};
|
|
54
|
+
/**
|
|
55
|
+
* An integer value node. Decodes to `number` when the magnitude fits in
|
|
56
|
+
* 2^53 - 1, else `bigint` (TOML integers span the full signed 64-bit range).
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
var TomlInteger = class extends Schema.TaggedClass()("TomlInteger", {
|
|
61
|
+
value: Schema.Union([Schema.Number, Schema.BigInt]),
|
|
62
|
+
offset: Schema.Number,
|
|
63
|
+
length: Schema.Number
|
|
64
|
+
}) {};
|
|
65
|
+
/**
|
|
66
|
+
* A float value node, including the special spellings (`inf`, `nan`).
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
var TomlFloat = class extends Schema.TaggedClass()("TomlFloat", {
|
|
71
|
+
value: Schema.Number,
|
|
72
|
+
offset: Schema.Number,
|
|
73
|
+
length: Schema.Number
|
|
74
|
+
}) {};
|
|
75
|
+
/**
|
|
76
|
+
* A boolean value node.
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
var TomlBoolean = class extends Schema.TaggedClass()("TomlBoolean", {
|
|
81
|
+
value: Schema.Boolean,
|
|
82
|
+
offset: Schema.Number,
|
|
83
|
+
length: Schema.Number
|
|
84
|
+
}) {};
|
|
85
|
+
/**
|
|
86
|
+
* A date-time value node wrapping one of the four TOML date-time classes.
|
|
87
|
+
*
|
|
88
|
+
* @public
|
|
89
|
+
*/
|
|
90
|
+
var TomlDateTimeLiteral = class extends Schema.TaggedClass()("TomlDateTimeLiteral", {
|
|
91
|
+
value: Schema.Union([
|
|
92
|
+
TomlOffsetDateTime,
|
|
93
|
+
TomlLocalDateTime,
|
|
94
|
+
TomlLocalDate,
|
|
95
|
+
TomlLocalTime
|
|
96
|
+
]),
|
|
97
|
+
offset: Schema.Number,
|
|
98
|
+
length: Schema.Number
|
|
99
|
+
}) {};
|
|
100
|
+
/**
|
|
101
|
+
* An array value node. Heterogeneous per TOML 1.0.0; may span multiple lines
|
|
102
|
+
* (the span covers brackets, inner newlines and inner comments).
|
|
103
|
+
*
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
var TomlArray = class extends Schema.TaggedClass()("TomlArray", {
|
|
107
|
+
items: Schema.Array(Schema.suspend(() => TomlValueNode)),
|
|
108
|
+
offset: Schema.Number,
|
|
109
|
+
length: Schema.Number
|
|
110
|
+
}) {};
|
|
111
|
+
/**
|
|
112
|
+
* One `key = value` entry inside an inline table. `keyPath` has more than one
|
|
113
|
+
* element for dotted keys (`{a.b = 1}`).
|
|
114
|
+
*
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
117
|
+
var TomlInlineEntry = class extends Schema.TaggedClass()("TomlInlineEntry", {
|
|
118
|
+
keyPath: Schema.Array(TomlKey),
|
|
119
|
+
value: Schema.suspend(() => TomlValueNode),
|
|
120
|
+
offset: Schema.Number,
|
|
121
|
+
length: Schema.Number
|
|
122
|
+
}) {};
|
|
123
|
+
/**
|
|
124
|
+
* An inline table value node (`{ k = v, ... }`). Single-line by grammar.
|
|
125
|
+
*
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
var TomlInlineTable = class extends Schema.TaggedClass()("TomlInlineTable", {
|
|
129
|
+
entries: Schema.Array(TomlInlineEntry),
|
|
130
|
+
offset: Schema.Number,
|
|
131
|
+
length: Schema.Number
|
|
132
|
+
}) {};
|
|
133
|
+
/**
|
|
134
|
+
* A discriminated-union schema covering all seven TOML value node types.
|
|
135
|
+
* Defined lazily via `Schema.suspend` to break the recursive reference chain
|
|
136
|
+
* `TomlValueNode → TomlArray/TomlInlineTable → TomlValueNode`.
|
|
137
|
+
*
|
|
138
|
+
* @public
|
|
139
|
+
*/
|
|
140
|
+
const TomlValueNode = Schema.suspend(() => Schema.Union([
|
|
141
|
+
TomlString,
|
|
142
|
+
TomlInteger,
|
|
143
|
+
TomlFloat,
|
|
144
|
+
TomlBoolean,
|
|
145
|
+
TomlDateTimeLiteral,
|
|
146
|
+
TomlArray,
|
|
147
|
+
TomlInlineTable
|
|
148
|
+
]));
|
|
149
|
+
/**
|
|
150
|
+
* A `key = value` expression. The span starts at the first character of the
|
|
151
|
+
* line's leading whitespace and ends after the terminating newline (or at
|
|
152
|
+
* EOF); multi-line values extend it. `comment` holds the decoded trailing
|
|
153
|
+
* comment (without `#`, one leading space stripped) when present.
|
|
154
|
+
*
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
var TomlKeyValue = class extends Schema.TaggedClass()("TomlKeyValue", {
|
|
158
|
+
keyPath: Schema.Array(TomlKey),
|
|
159
|
+
value: Schema.suspend(() => TomlValueNode),
|
|
160
|
+
comment: Schema.optionalKey(Schema.String),
|
|
161
|
+
offset: Schema.Number,
|
|
162
|
+
length: Schema.Number
|
|
163
|
+
}) {};
|
|
164
|
+
/**
|
|
165
|
+
* A `[table]` header expression. Span contract as in {@link TomlKeyValue}.
|
|
166
|
+
*
|
|
167
|
+
* @public
|
|
168
|
+
*/
|
|
169
|
+
var TomlTableHeader = class extends Schema.TaggedClass()("TomlTableHeader", {
|
|
170
|
+
keyPath: Schema.Array(TomlKey),
|
|
171
|
+
comment: Schema.optionalKey(Schema.String),
|
|
172
|
+
offset: Schema.Number,
|
|
173
|
+
length: Schema.Number
|
|
174
|
+
}) {};
|
|
175
|
+
/**
|
|
176
|
+
* A `[[array-of-tables]]` header expression. Span contract as in
|
|
177
|
+
* {@link TomlKeyValue}.
|
|
178
|
+
*
|
|
179
|
+
* @public
|
|
180
|
+
*/
|
|
181
|
+
var TomlArrayTableHeader = class extends Schema.TaggedClass()("TomlArrayTableHeader", {
|
|
182
|
+
keyPath: Schema.Array(TomlKey),
|
|
183
|
+
comment: Schema.optionalKey(Schema.String),
|
|
184
|
+
offset: Schema.Number,
|
|
185
|
+
length: Schema.Number
|
|
186
|
+
}) {};
|
|
187
|
+
/**
|
|
188
|
+
* A run of consecutive blank and comment-only lines, coalesced into one
|
|
189
|
+
* expression. `text` is the raw source slice, newlines included.
|
|
190
|
+
*
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
193
|
+
var TomlTrivia = class extends Schema.TaggedClass()("TomlTrivia", {
|
|
194
|
+
text: Schema.String,
|
|
195
|
+
offset: Schema.Number,
|
|
196
|
+
length: Schema.Number
|
|
197
|
+
}) {};
|
|
198
|
+
/**
|
|
199
|
+
* The union schema of the four expression types making up a document's
|
|
200
|
+
* linear CST.
|
|
201
|
+
*
|
|
202
|
+
* @public
|
|
203
|
+
*/
|
|
204
|
+
const TomlExpression = Schema.Union([
|
|
205
|
+
TomlKeyValue,
|
|
206
|
+
TomlTableHeader,
|
|
207
|
+
TomlArrayTableHeader,
|
|
208
|
+
TomlTrivia
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
export { TomlArray, TomlArrayTableHeader, TomlBoolean, TomlDateTimeLiteral, TomlExpression, TomlFloat, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlString, TomlStringStyle, TomlTableHeader, TomlTrivia, TomlValueNode };
|
package/TomlVisitor.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { isRawTomlError } from "./internal/diagnostics.js";
|
|
2
|
+
import { isGuardExceeded } from "./internal/limits.js";
|
|
3
|
+
import { TomlKeyValue, TomlTrivia } from "./TomlNode.js";
|
|
4
|
+
import { scanComment, scanNewline, scanWhitespace } from "./internal/scanner.js";
|
|
5
|
+
import { parseExpressions } from "./internal/parser.js";
|
|
6
|
+
import { analyze } from "./internal/semantic.js";
|
|
7
|
+
import { TomlDiagnostic } from "./TomlDiagnostic.js";
|
|
8
|
+
import { TomlParseError } from "./Toml.js";
|
|
9
|
+
import { Data, Effect, Stream } from "effect";
|
|
10
|
+
|
|
11
|
+
//#region src/TomlVisitor.ts
|
|
12
|
+
/**
|
|
13
|
+
* Constructors and matchers for the `TomlVisitorEvent` union (e.g.
|
|
14
|
+
* `TomlVisitorEvent.KeyValue({ path, node })`, `TomlVisitorEvent.$is("Comment")`).
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
const TomlVisitorEvent = Data.taggedEnum();
|
|
19
|
+
/** Decoded trailing-comment text: the raw text after `#` with one leading space stripped (mirrors internal/parser.ts). */
|
|
20
|
+
const decodeCommentText = (raw) => raw.startsWith(" ") ? raw.slice(1) : raw;
|
|
21
|
+
/**
|
|
22
|
+
* Locate the `#` starting a trailing comment already known to exist
|
|
23
|
+
* (`comment !== undefined`). Search begins after the last span that can
|
|
24
|
+
* itself contain a `#` character — the value span for a key-value, the last
|
|
25
|
+
* key span for a header — so the first `#` found is unambiguously the
|
|
26
|
+
* comment marker.
|
|
27
|
+
*/
|
|
28
|
+
const trailingCommentOffset = (source, expression) => {
|
|
29
|
+
if (expression instanceof TomlKeyValue) return source.indexOf("#", expression.value.offset + expression.value.length);
|
|
30
|
+
const lastKey = expression.keyPath[expression.keyPath.length - 1];
|
|
31
|
+
return source.indexOf("#", lastKey.offset + lastKey.length);
|
|
32
|
+
};
|
|
33
|
+
/** Standalone comments inside a trivia run, each positioned by its `#`'s absolute offset. */
|
|
34
|
+
const collectTriviaComments = (trivia) => {
|
|
35
|
+
const events = [];
|
|
36
|
+
const text = trivia.text;
|
|
37
|
+
let pos = 0;
|
|
38
|
+
while (pos < text.length) {
|
|
39
|
+
const i = scanWhitespace(text, pos);
|
|
40
|
+
if (text.charAt(i) === "#") {
|
|
41
|
+
const scanned = scanComment(text, i);
|
|
42
|
+
const offset = trivia.offset + i;
|
|
43
|
+
events.push({
|
|
44
|
+
offset,
|
|
45
|
+
event: TomlVisitorEvent.Comment({
|
|
46
|
+
text: decodeCommentText(scanned.value),
|
|
47
|
+
offset
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
pos = scanned.end < text.length ? scanNewline(text, scanned.end) : scanned.end;
|
|
51
|
+
} else pos = i < text.length ? scanNewline(text, i) : i;
|
|
52
|
+
}
|
|
53
|
+
return events;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Parse, run the semantic pass collecting its callbacks as positioned events,
|
|
57
|
+
* collect comment events from the same expression array, then merge both by
|
|
58
|
+
* offset. Throws the engine's raw carriers on malformed/adversarial input —
|
|
59
|
+
* caught and materialized by the caller, exactly like `Toml.parse`.
|
|
60
|
+
*/
|
|
61
|
+
const collectEvents = (text) => {
|
|
62
|
+
const expressions = parseExpressions(text);
|
|
63
|
+
const positioned = [];
|
|
64
|
+
analyze(expressions, {
|
|
65
|
+
onTableStart: (path, header) => {
|
|
66
|
+
positioned.push({
|
|
67
|
+
offset: header?.offset ?? -1,
|
|
68
|
+
event: TomlVisitorEvent.TableStart({ path })
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
onArrayTableStart: (path, index, header) => {
|
|
72
|
+
positioned.push({
|
|
73
|
+
offset: header.offset,
|
|
74
|
+
event: TomlVisitorEvent.ArrayTableStart({
|
|
75
|
+
path,
|
|
76
|
+
index
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
onKeyValue: (path, node) => {
|
|
81
|
+
positioned.push({
|
|
82
|
+
offset: node.offset,
|
|
83
|
+
event: TomlVisitorEvent.KeyValue({
|
|
84
|
+
path,
|
|
85
|
+
node
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
for (const expression of expressions) {
|
|
91
|
+
if (expression instanceof TomlTrivia) {
|
|
92
|
+
positioned.push(...collectTriviaComments(expression));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (expression.comment !== void 0) {
|
|
96
|
+
const offset = trailingCommentOffset(text, expression);
|
|
97
|
+
positioned.push({
|
|
98
|
+
offset,
|
|
99
|
+
event: TomlVisitorEvent.Comment({
|
|
100
|
+
text: expression.comment,
|
|
101
|
+
offset
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
positioned.sort((a, b) => a.offset - b.offset);
|
|
107
|
+
return positioned.map((positionedEvent) => positionedEvent.event);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Run `collectEvents`, materializing the engine's raw carriers into
|
|
111
|
+
* {@link TomlParseError} exactly like `Toml.parse` — a lex/parse error and a
|
|
112
|
+
* semantic-pass error (e.g. `DuplicateKey`) both fail the same way.
|
|
113
|
+
*/
|
|
114
|
+
const collectEventsOrFail = (text) => Effect.try({
|
|
115
|
+
try: () => collectEvents(text),
|
|
116
|
+
catch: (defect) => {
|
|
117
|
+
if (isRawTomlError(defect)) return new TomlParseError({ diagnostics: [TomlDiagnostic.fromRaw(text, defect.diagnostic)] });
|
|
118
|
+
if (isGuardExceeded(defect)) return new TomlParseError({ diagnostics: [TomlDiagnostic.fromRaw(text, {
|
|
119
|
+
code: "NestingDepthExceeded",
|
|
120
|
+
message: defect.message,
|
|
121
|
+
offset: defect.offset,
|
|
122
|
+
length: 0
|
|
123
|
+
})] });
|
|
124
|
+
throw defect;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* SAX-style TOML visitor statics. Not instantiable.
|
|
129
|
+
*
|
|
130
|
+
* @public
|
|
131
|
+
*/
|
|
132
|
+
var TomlVisitor = class {
|
|
133
|
+
constructor() {}
|
|
134
|
+
/**
|
|
135
|
+
* Create a `Stream` of `TomlVisitorEvent` from TOML text, in document
|
|
136
|
+
* order. Fails the stream with {@link TomlParseError} on the first
|
|
137
|
+
* lex/parse or semantic violation — never as an unhandled defect.
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* Construction is eager, not on-demand: the full text is parsed, walked by
|
|
141
|
+
* `analyze` and sorted into document order up front, inside the `Effect`
|
|
142
|
+
* `Stream.unwrap` runs to build the stream. Only enumeration of the
|
|
143
|
+
* already-built event array is streamed — `Stream.take` still short-circuits
|
|
144
|
+
* consumption, but it does not avoid the initial parse/analyze/sort pass.
|
|
145
|
+
*/
|
|
146
|
+
static visit(text) {
|
|
147
|
+
return Stream.unwrap(collectEventsOrFail(text).pipe(Effect.map(Stream.fromIterable)));
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
export { TomlVisitor, TomlVisitorEvent };
|