@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/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 };