@effected/toml 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/Toml.js +164 -32
- package/TomlNode.js +3 -2
- package/index.d.ts +154 -16
- package/internal/diagnostics.js +0 -2
- package/internal/parser.js +25 -29
- package/internal/scanner.js +31 -17
- package/package.json +5 -4
- package/tsdoc-metadata.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@effected/toml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
|
-
Zero-dependency TOML 1.
|
|
8
|
+
Zero-dependency TOML 1.1.0 parsing, editing and formatting expressed as Effect schemas and pure functions. Parse TOML into plain values or a byte-exact linear CST, compute comment-preserving edits, format, modify by path, walk a document as a `Stream`, and decode straight into a validated domain schema.
|
|
9
9
|
|
|
10
10
|
> **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
|
|
11
11
|
> development against a single pinned Effect v4 beta. Packages graduate to
|
|
@@ -23,7 +23,7 @@ Zero-dependency TOML 1.0.0 parsing, editing and formatting expressed as Effect s
|
|
|
23
23
|
|
|
24
24
|
TOML is the config format behind `Cargo.toml`, `pyproject.toml` and a large share of the Rust and Python toolchains — files that people maintain by hand and comment heavily. Most JavaScript TOML libraries round-trip through a plain-object model, which loses every comment and blank line the moment you write the file back out.
|
|
25
25
|
|
|
26
|
-
The engine here is written from scratch against the TOML 1.
|
|
26
|
+
The engine here is written from scratch against the TOML 1.1.0 spec, and it is the only format package in this repo that vendors no upstream code at all. Parsing accepts everything 1.1 added — newlines, comments and trailing commas inside inline tables, optional seconds in times, and the `\e` and `\xHH` string escapes — while `Toml.stringify` deliberately emits 1.0 spellings, which are valid 1.1, so documents this package writes stay readable by 1.0-only consumers. It parses into a lossless linear CST whose expression spans tile the source byte-exact, so `TomlDocument.parse(text).stringify()` reproduces the original text exactly for any valid document. There is no separate re-serialization path that can drift from the source. Edits are byte-minimal splices against that CST, so formatting and path-based modification preserve every comment and every byte you did not touch.
|
|
27
27
|
|
|
28
28
|
The value model is honest about TOML's types rather than flattening them into JavaScript's. Integers past ±(2^53 − 1) decode to `bigint` instead of silently losing precision, and TOML's four date-time types decode to four calendar-validated value classes instead of a `Date` that cannot represent a local time. TOML has no null, so `Toml.stringify` on a value containing `null` fails with a structured `UnsupportedValue` diagnostic naming the offending path rather than dropping the key. Every fallible entry point carries a typed error built from `TomlDiagnostic`, and nesting-depth guards on both the parse and the stringify side mean hostile input fails through that channel rather than as a stack overflow.
|
|
29
29
|
|
|
@@ -39,6 +39,8 @@ pnpm add @effected/toml effect
|
|
|
39
39
|
|
|
40
40
|
Requires Node.js >=24.11.0. `effect` v4 is a peer dependency; the package itself adds no other runtime dependencies.
|
|
41
41
|
|
|
42
|
+
All `@effected/*` packages are ESM-only: the exports maps publish only `import` conditions, so `require()` — including tools that resolve in CJS mode — fails with Node's `ERR_PACKAGE_PATH_NOT_EXPORTED` rather than loading a CJS build that does not exist. Import from an ES module.
|
|
43
|
+
|
|
42
44
|
## Quick start
|
|
43
45
|
|
|
44
46
|
Compose your schema with `Toml.schema` to decode TOML straight into a validated domain value, or reach for the pre-bound `Toml.TomlFromString` codec when you just want the plain value:
|
|
@@ -133,7 +135,7 @@ Effect.runPromise(Effect.result(Toml.stringify({ a: null }))).then(console.log);
|
|
|
133
135
|
|
|
134
136
|
## Conformance
|
|
135
137
|
|
|
136
|
-
The engine runs the [toml-test](https://github.com/toml-lang/toml-test) 1.
|
|
138
|
+
The engine runs the [toml-test](https://github.com/toml-lang/toml-test) compliance corpus — its `files-toml-1.1.0` subset, 214 valid cases and 467 invalid ones — to a 100% pass rate with no skip list, and a differential property suite cross-checks it against an independent TOML parser. Both oracles are devDependency-only; neither reaches your runtime.
|
|
137
139
|
|
|
138
140
|
## License
|
|
139
141
|
|
package/Toml.js
CHANGED
|
@@ -4,13 +4,20 @@ import { parseExpressions } from "./internal/parser.js";
|
|
|
4
4
|
import { buildValue } from "./internal/semantic.js";
|
|
5
5
|
import { stringifyValue } from "./internal/stringifyValue.js";
|
|
6
6
|
import { TomlDiagnostic } from "./TomlDiagnostic.js";
|
|
7
|
-
import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
7
|
+
import { Effect, Option, Result, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
8
8
|
|
|
9
9
|
//#region src/Toml.ts
|
|
10
10
|
/**
|
|
11
11
|
* Options controlling stringify behavior. The only knob is `newline` —
|
|
12
12
|
* omitted, it resolves to `"\n"`.
|
|
13
13
|
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Stringify deliberately emits only TOML 1.0.0 spellings — seconds always
|
|
16
|
+
* present in times, no `\e`/`\xHH` escapes, single-line inline tables — even
|
|
17
|
+
* though {@link Toml.parse} accepts the full TOML 1.1.0 grammar. Every 1.0
|
|
18
|
+
* document is valid 1.1, so this conservative-write/liberal-read asymmetry
|
|
19
|
+
* keeps emitted documents readable by 1.0-only consumers.
|
|
20
|
+
*
|
|
14
21
|
* @public
|
|
15
22
|
*/
|
|
16
23
|
var TomlStringifyOptions = class extends Schema.Class("TomlStringifyOptions")({ newline: Schema.optionalKey(Schema.Literals(["\n", "\r\n"])) }) {};
|
|
@@ -47,34 +54,38 @@ var TomlStringifyError = class extends Schema.TaggedErrorClass()("TomlStringifyE
|
|
|
47
54
|
* Run the parser and semantic pass, materializing the engine's raw carriers
|
|
48
55
|
* into the typed error: `RawTomlError` becomes a positioned diagnostic and a
|
|
49
56
|
* `GuardExceeded` depth trip becomes a `NestingDepthExceeded` diagnostic
|
|
50
|
-
* (never an unhandled defect). Anything else is a genuine defect and rethrows
|
|
57
|
+
* (never an unhandled defect). Anything else is a genuine defect and rethrows
|
|
58
|
+
* — synchronously to a `Result` caller, and as a `Die` through the `Effect`
|
|
59
|
+
* forms, which evaluate this inside the effect.
|
|
51
60
|
*/
|
|
52
|
-
const
|
|
53
|
-
try
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
61
|
+
const parseToResult = (text) => {
|
|
62
|
+
try {
|
|
63
|
+
return Result.succeed(buildValue(parseExpressions(text)));
|
|
64
|
+
} catch (defect) {
|
|
65
|
+
if (isRawTomlError(defect)) return Result.fail(new TomlParseError({ diagnostics: [TomlDiagnostic.fromRaw(text, defect.diagnostic)] }));
|
|
66
|
+
if (isGuardExceeded(defect)) return Result.fail(new TomlParseError({ diagnostics: [TomlDiagnostic.fromRaw(text, {
|
|
57
67
|
code: "NestingDepthExceeded",
|
|
58
68
|
message: defect.message,
|
|
59
69
|
offset: defect.offset,
|
|
60
70
|
length: 0
|
|
61
|
-
})] });
|
|
71
|
+
})] }));
|
|
62
72
|
throw defect;
|
|
63
73
|
}
|
|
64
|
-
}
|
|
65
|
-
const
|
|
66
|
-
try
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
74
|
+
};
|
|
75
|
+
const stringifyToResult = (value, options) => {
|
|
76
|
+
try {
|
|
77
|
+
return Result.succeed(stringifyValue(value, options?.newline ?? "\n"));
|
|
78
|
+
} catch (defect) {
|
|
79
|
+
if (isRawTomlError(defect)) return Result.fail(new TomlStringifyError({ diagnostic: TomlDiagnostic.fromRaw("", defect.diagnostic) }));
|
|
80
|
+
if (isGuardExceeded(defect)) return Result.fail(new TomlStringifyError({ diagnostic: TomlDiagnostic.fromRaw("", {
|
|
70
81
|
code: "NestingDepthExceeded",
|
|
71
82
|
message: defect.message,
|
|
72
83
|
offset: 0,
|
|
73
84
|
length: 0
|
|
74
|
-
}) });
|
|
85
|
+
}) }));
|
|
75
86
|
throw defect;
|
|
76
87
|
}
|
|
77
|
-
}
|
|
88
|
+
};
|
|
78
89
|
/**
|
|
79
90
|
* Static entry points for TOML parsing, stringification and the schema
|
|
80
91
|
* factories. Not instantiable.
|
|
@@ -84,7 +95,7 @@ const stringifyOrFail = (value, options) => Effect.try({
|
|
|
84
95
|
* channels — including the hardening guards (nesting-depth caps on both
|
|
85
96
|
* sides, circular-reference detection on encode) that keep malformed or
|
|
86
97
|
* adversarial input on the typed channel instead of surfacing as an
|
|
87
|
-
* unhandled defect. `parse` takes no options: TOML 1.
|
|
98
|
+
* unhandled defect. `parse` takes no options: TOML 1.1.0 parsing has no
|
|
88
99
|
* knobs.
|
|
89
100
|
*
|
|
90
101
|
* @example
|
|
@@ -103,32 +114,110 @@ const stringifyOrFail = (value, options) => Effect.try({
|
|
|
103
114
|
var Toml = class Toml {
|
|
104
115
|
constructor() {}
|
|
105
116
|
/**
|
|
106
|
-
* Parse a TOML 1.
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* `bigint` past 2^53) and
|
|
110
|
-
*
|
|
111
|
-
* returns `unknown`, never
|
|
117
|
+
* Parse a TOML 1.1.0 document into a plain JavaScript value, synchronously,
|
|
118
|
+
* returning a `Result` instead of an `Effect`: tables and inline tables
|
|
119
|
+
* become plain objects (`__proto__` lands as an own data property), arrays
|
|
120
|
+
* become plain arrays, integers become `number` (or `bigint` past 2^53) and
|
|
121
|
+
* date-times become the four `TomlDateTime` classes. Fails with
|
|
122
|
+
* {@link TomlParseError} at the first violation; returns `unknown`, never
|
|
123
|
+
* `any`.
|
|
112
124
|
*
|
|
113
125
|
* A nesting-depth bomb (arrays or inline tables past the engine cap) also
|
|
114
126
|
* fails through {@link TomlParseError} with a `NestingDepthExceeded`
|
|
115
127
|
* diagnostic, never as an unhandled defect.
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* {@link Toml.parse} is defined in terms of this function; the two never
|
|
131
|
+
* diverge. Reach for the `Effect` variant inside Effect code — it carries
|
|
132
|
+
* the `Toml.parse` tracing span — and for this one at synchronous
|
|
133
|
+
* boundaries such as a lint-staged handler.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { Toml } from "@effected/toml";
|
|
138
|
+
* import { Result } from "effect";
|
|
139
|
+
*
|
|
140
|
+
* const ok = Toml.parseResult('name = "Alice"');
|
|
141
|
+
* if (Result.isSuccess(ok)) {
|
|
142
|
+
* console.log(ok.success); // => { name: "Alice" }
|
|
143
|
+
* }
|
|
144
|
+
*
|
|
145
|
+
* const bad = Toml.parseResult("name = ");
|
|
146
|
+
* if (Result.isFailure(bad)) {
|
|
147
|
+
* console.log(bad.failure._tag); // => "TomlParseError"
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @param text - The TOML source to parse.
|
|
152
|
+
* @returns A `Result` succeeding with the decoded value (`unknown`, never
|
|
153
|
+
* `any`), or failing with {@link TomlParseError}.
|
|
154
|
+
*/
|
|
155
|
+
static parseResult(text) {
|
|
156
|
+
return parseToResult(text);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Parse a TOML 1.1.0 document into a plain JavaScript value. Defined in
|
|
160
|
+
* terms of {@link Toml.parseResult} — synchronous callers can use that
|
|
161
|
+
* variant directly.
|
|
162
|
+
*
|
|
163
|
+
* @param text - The TOML source to parse.
|
|
164
|
+
* @returns An `Effect` that succeeds with the decoded value, or fails with
|
|
165
|
+
* {@link TomlParseError}.
|
|
116
166
|
*/
|
|
117
|
-
static parse = Effect.fn("Toml.parse")(
|
|
118
|
-
return yield* parseOrFail(text);
|
|
119
|
-
});
|
|
167
|
+
static parse = Effect.fn("Toml.parse")((text) => Effect.fromResult(Toml.parseResult(text)));
|
|
120
168
|
/**
|
|
121
|
-
* Stringify a plain JavaScript value as a canonical TOML document
|
|
122
|
-
*
|
|
169
|
+
* Stringify a plain JavaScript value as a canonical TOML document,
|
|
170
|
+
* synchronously, returning a `Result` instead of an `Effect`: within a
|
|
171
|
+
* table, non-table pairs first, then sub-tables as `[dotted.header]`
|
|
123
172
|
* sections depth-first, then arrays of tables as `[[dotted.header]]`
|
|
124
173
|
* sections, a blank line before every header except at document start.
|
|
125
174
|
* Fails with {@link TomlStringifyError} on unsupported values (TOML has no
|
|
126
175
|
* null), out-of-int64-range `bigint`s, circular references and
|
|
127
176
|
* depth-guard trips — all on the typed channel.
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* {@link Toml.stringify} is defined in terms of this function; the two
|
|
180
|
+
* never diverge. Reach for the `Effect` variant inside Effect code — it
|
|
181
|
+
* carries the `Toml.stringify` tracing span — and for this one at
|
|
182
|
+
* synchronous boundaries.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* import { Toml } from "@effected/toml";
|
|
187
|
+
* import { Result } from "effect";
|
|
188
|
+
*
|
|
189
|
+
* const ok = Toml.stringifyResult({ name: "Alice" });
|
|
190
|
+
* if (Result.isSuccess(ok)) {
|
|
191
|
+
* console.log(ok.success); // => 'name = "Alice"\n'
|
|
192
|
+
* }
|
|
193
|
+
*
|
|
194
|
+
* const bad = Toml.stringifyResult({ nope: null });
|
|
195
|
+
* if (Result.isFailure(bad)) {
|
|
196
|
+
* console.log(bad.failure._tag); // => "TomlStringifyError"
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @param value - The plain JavaScript value to stringify.
|
|
201
|
+
* @param options - Optional {@link TomlStringifyOptions}; `newline`
|
|
202
|
+
* defaults to `"\n"`.
|
|
203
|
+
* @returns A `Result` succeeding with the TOML text, or failing with
|
|
204
|
+
* {@link TomlStringifyError}.
|
|
205
|
+
*/
|
|
206
|
+
static stringifyResult(value, options) {
|
|
207
|
+
return stringifyToResult(value, options);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Stringify a plain JavaScript value as a canonical TOML document. Defined
|
|
211
|
+
* in terms of {@link Toml.stringifyResult} — synchronous callers can use
|
|
212
|
+
* that variant directly.
|
|
213
|
+
*
|
|
214
|
+
* @param value - The plain JavaScript value to stringify.
|
|
215
|
+
* @param options - Optional {@link TomlStringifyOptions}; `newline`
|
|
216
|
+
* defaults to `"\n"`.
|
|
217
|
+
* @returns An `Effect` that succeeds with the TOML text, or fails with
|
|
218
|
+
* {@link TomlStringifyError}.
|
|
128
219
|
*/
|
|
129
|
-
static stringify = Effect.fn("Toml.stringify")(
|
|
130
|
-
return yield* stringifyOrFail(value, options);
|
|
131
|
-
});
|
|
220
|
+
static stringify = Effect.fn("Toml.stringify")((value, options) => Effect.fromResult(Toml.stringifyResult(value, options)));
|
|
132
221
|
/**
|
|
133
222
|
* A `Schema<unknown, string>` decoding a TOML document and encoding values
|
|
134
223
|
* back to canonical TOML text.
|
|
@@ -140,7 +229,7 @@ var Toml = class Toml {
|
|
|
140
229
|
static fromString() {
|
|
141
230
|
return Schema.String.pipe(Schema.decodeTo(Schema.Unknown, SchemaTransformation.transformOrFail({
|
|
142
231
|
decode: (input) => Toml.parse(input).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue(Option.some(input), { message: error.message }))),
|
|
143
|
-
encode: (value) =>
|
|
232
|
+
encode: (value) => Effect.fromResult(Toml.stringifyResult(value)).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue(Option.some(value), { message: error.message })))
|
|
144
233
|
})));
|
|
145
234
|
}
|
|
146
235
|
/**
|
|
@@ -159,6 +248,49 @@ var Toml = class Toml {
|
|
|
159
248
|
static schema(target) {
|
|
160
249
|
return Toml.TomlFromString.pipe(Schema.decodeTo(target));
|
|
161
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Bind a target schema to the TOML codec once, yielding the composed
|
|
253
|
+
* schema plus pre-derived `decode`/`encode` directions — the
|
|
254
|
+
* {@link Toml.schema} composition without the generic `Schema` machinery
|
|
255
|
+
* at every use site. Binds the plain form only: TOML 1.1.0 parsing on
|
|
256
|
+
* decode, default stringify options on encode.
|
|
257
|
+
*
|
|
258
|
+
* Both directions fail with `Schema.SchemaError`, exactly as
|
|
259
|
+
* `Schema.decodeEffect`/`Schema.encodeEffect` over {@link Toml.schema}
|
|
260
|
+
* would; the target's decoding/encoding service requirements flow through.
|
|
261
|
+
*
|
|
262
|
+
* @remarks
|
|
263
|
+
* Schema-producing: each call composes a fresh schema and derives both
|
|
264
|
+
* directions from it. Bind the result to a `const` — that single binding is
|
|
265
|
+
* the point.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* import { Toml } from "@effected/toml";
|
|
270
|
+
* import { Effect, Schema } from "effect";
|
|
271
|
+
*
|
|
272
|
+
* const Config = Schema.Struct({ name: Schema.String });
|
|
273
|
+
* const config = Toml.bind(Config);
|
|
274
|
+
*
|
|
275
|
+
* const program = Effect.gen(function* () {
|
|
276
|
+
* const value = yield* config.decode('name = "Alice"');
|
|
277
|
+
* const text = yield* config.encode(value);
|
|
278
|
+
* return [value, text] as const;
|
|
279
|
+
* });
|
|
280
|
+
* ```
|
|
281
|
+
*
|
|
282
|
+
* @param target - The domain schema decoded values must satisfy.
|
|
283
|
+
* @returns A {@link TomlBoundCodec} carrying the composed schema and its
|
|
284
|
+
* two pre-bound directions.
|
|
285
|
+
*/
|
|
286
|
+
static bind(target) {
|
|
287
|
+
const schema = Toml.schema(target);
|
|
288
|
+
return {
|
|
289
|
+
schema,
|
|
290
|
+
decode: Schema.decodeEffect(schema),
|
|
291
|
+
encode: Schema.encodeEffect(schema)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
162
294
|
};
|
|
163
295
|
|
|
164
296
|
//#endregion
|
package/TomlNode.js
CHANGED
|
@@ -98,7 +98,7 @@ var TomlDateTimeLiteral = class extends Schema.TaggedClass()("TomlDateTimeLitera
|
|
|
98
98
|
length: Schema.Number
|
|
99
99
|
}) {};
|
|
100
100
|
/**
|
|
101
|
-
* An array value node. Heterogeneous per TOML
|
|
101
|
+
* An array value node. Heterogeneous per TOML; may span multiple lines
|
|
102
102
|
* (the span covers brackets, inner newlines and inner comments).
|
|
103
103
|
*
|
|
104
104
|
* @public
|
|
@@ -121,7 +121,8 @@ var TomlInlineEntry = class extends Schema.TaggedClass()("TomlInlineEntry", {
|
|
|
121
121
|
length: Schema.Number
|
|
122
122
|
}) {};
|
|
123
123
|
/**
|
|
124
|
-
* An inline table value node (`{ k = v, ... }`).
|
|
124
|
+
* An inline table value node (`{ k = v, ... }`). May span multiple lines
|
|
125
|
+
* since TOML 1.1 (the span covers braces, inner newlines and inner comments).
|
|
125
126
|
*
|
|
126
127
|
* @public
|
|
127
128
|
*/
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect, Schema, Stream } from "effect";
|
|
1
|
+
import { Data, Effect, Result, Schema, Stream } from "effect";
|
|
2
2
|
//#region src/TomlDiagnostic.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Error codes emitted by the lexer stage.
|
|
@@ -17,7 +17,7 @@ type TomlLexErrorCode = typeof TomlLexErrorCode.Type;
|
|
|
17
17
|
*
|
|
18
18
|
* @public
|
|
19
19
|
*/
|
|
20
|
-
declare const TomlParseErrorCode: Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "
|
|
20
|
+
declare const TomlParseErrorCode: Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "InvalidValue", "InvalidNumber", "IntegerOutOfRange", "InvalidDateTime", "NestingDepthExceeded"]>;
|
|
21
21
|
/**
|
|
22
22
|
* The union of all parser-stage error code string literals.
|
|
23
23
|
*
|
|
@@ -54,7 +54,7 @@ type TomlStringifyErrorCode = typeof TomlStringifyErrorCode.Type;
|
|
|
54
54
|
*
|
|
55
55
|
* @public
|
|
56
56
|
*/
|
|
57
|
-
declare const TomlErrorCode: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "
|
|
57
|
+
declare const TomlErrorCode: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "InvalidValue", "InvalidNumber", "IntegerOutOfRange", "InvalidDateTime", "NestingDepthExceeded"]>, Schema.Literals<readonly ["DuplicateKey", "TableRedefined", "ArrayOfTablesConflict", "DottedKeyConflict", "InlineTableExtended"]>, Schema.Literals<readonly ["CircularReference", "UnsupportedValue", "IntegerOutOfRange", "NestingDepthExceeded"]>]>;
|
|
58
58
|
/**
|
|
59
59
|
* The union of all TOML error code string literals.
|
|
60
60
|
*
|
|
@@ -62,7 +62,7 @@ declare const TomlErrorCode: Schema.Union<readonly [Schema.Literals<readonly ["I
|
|
|
62
62
|
*/
|
|
63
63
|
type TomlErrorCode = typeof TomlErrorCode.Type;
|
|
64
64
|
declare const TomlDiagnostic_base: Schema.Class<TomlDiagnostic, Schema.Struct<{
|
|
65
|
-
readonly code: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "
|
|
65
|
+
readonly code: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "InvalidValue", "InvalidNumber", "IntegerOutOfRange", "InvalidDateTime", "NestingDepthExceeded"]>, Schema.Literals<readonly ["DuplicateKey", "TableRedefined", "ArrayOfTablesConflict", "DottedKeyConflict", "InlineTableExtended"]>, Schema.Literals<readonly ["CircularReference", "UnsupportedValue", "IntegerOutOfRange", "NestingDepthExceeded"]>]>;
|
|
66
66
|
readonly message: Schema.String;
|
|
67
67
|
readonly offset: Schema.Number;
|
|
68
68
|
readonly length: Schema.Number;
|
|
@@ -104,6 +104,13 @@ declare const TomlStringifyOptions_base: Schema.Class<TomlStringifyOptions, Sche
|
|
|
104
104
|
* Options controlling stringify behavior. The only knob is `newline` —
|
|
105
105
|
* omitted, it resolves to `"\n"`.
|
|
106
106
|
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* Stringify deliberately emits only TOML 1.0.0 spellings — seconds always
|
|
109
|
+
* present in times, no `\e`/`\xHH` escapes, single-line inline tables — even
|
|
110
|
+
* though {@link Toml.parse} accepts the full TOML 1.1.0 grammar. Every 1.0
|
|
111
|
+
* document is valid 1.1, so this conservative-write/liberal-read asymmetry
|
|
112
|
+
* keeps emitted documents readable by 1.0-only consumers.
|
|
113
|
+
*
|
|
107
114
|
* @public
|
|
108
115
|
*/
|
|
109
116
|
declare class TomlStringifyOptions extends TomlStringifyOptions_base {}
|
|
@@ -135,6 +142,22 @@ declare const TomlStringifyError_base: Schema.Class<TomlStringifyError, Schema.T
|
|
|
135
142
|
declare class TomlStringifyError extends TomlStringifyError_base {
|
|
136
143
|
get message(): string;
|
|
137
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* A domain codec pre-bound to its two directions, returned by
|
|
147
|
+
* {@link Toml.bind}: the composed `schema` (what {@link Toml.schema} returns)
|
|
148
|
+
* plus `decode` and `encode` functions derived from it once, so callers need
|
|
149
|
+
* no generic `Schema` machinery at the use site.
|
|
150
|
+
*
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
153
|
+
interface TomlBoundCodec<T, RD = never, RE = never> {
|
|
154
|
+
/** The composed codec decoding a TOML `string` straight into `T`. */
|
|
155
|
+
readonly schema: Schema.Codec<T, string, RD, RE>;
|
|
156
|
+
/** Decode TOML text into a validated `T`. */
|
|
157
|
+
readonly decode: (text: string) => Effect.Effect<T, Schema.SchemaError, RD>;
|
|
158
|
+
/** Encode a `T` back to canonical TOML text. */
|
|
159
|
+
readonly encode: (value: T) => Effect.Effect<string, Schema.SchemaError, RE>;
|
|
160
|
+
}
|
|
138
161
|
/**
|
|
139
162
|
* Static entry points for TOML parsing, stringification and the schema
|
|
140
163
|
* factories. Not instantiable.
|
|
@@ -144,7 +167,7 @@ declare class TomlStringifyError extends TomlStringifyError_base {
|
|
|
144
167
|
* channels — including the hardening guards (nesting-depth caps on both
|
|
145
168
|
* sides, circular-reference detection on encode) that keep malformed or
|
|
146
169
|
* adversarial input on the typed channel instead of surfacing as an
|
|
147
|
-
* unhandled defect. `parse` takes no options: TOML 1.
|
|
170
|
+
* unhandled defect. `parse` takes no options: TOML 1.1.0 parsing has no
|
|
148
171
|
* knobs.
|
|
149
172
|
*
|
|
150
173
|
* @example
|
|
@@ -163,26 +186,104 @@ declare class TomlStringifyError extends TomlStringifyError_base {
|
|
|
163
186
|
declare class Toml {
|
|
164
187
|
private constructor();
|
|
165
188
|
/**
|
|
166
|
-
* Parse a TOML 1.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* `bigint` past 2^53) and
|
|
170
|
-
*
|
|
171
|
-
* returns `unknown`, never
|
|
189
|
+
* Parse a TOML 1.1.0 document into a plain JavaScript value, synchronously,
|
|
190
|
+
* returning a `Result` instead of an `Effect`: tables and inline tables
|
|
191
|
+
* become plain objects (`__proto__` lands as an own data property), arrays
|
|
192
|
+
* become plain arrays, integers become `number` (or `bigint` past 2^53) and
|
|
193
|
+
* date-times become the four `TomlDateTime` classes. Fails with
|
|
194
|
+
* {@link TomlParseError} at the first violation; returns `unknown`, never
|
|
195
|
+
* `any`.
|
|
172
196
|
*
|
|
173
197
|
* A nesting-depth bomb (arrays or inline tables past the engine cap) also
|
|
174
198
|
* fails through {@link TomlParseError} with a `NestingDepthExceeded`
|
|
175
199
|
* diagnostic, never as an unhandled defect.
|
|
200
|
+
*
|
|
201
|
+
* @remarks
|
|
202
|
+
* {@link Toml.parse} is defined in terms of this function; the two never
|
|
203
|
+
* diverge. Reach for the `Effect` variant inside Effect code — it carries
|
|
204
|
+
* the `Toml.parse` tracing span — and for this one at synchronous
|
|
205
|
+
* boundaries such as a lint-staged handler.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* import { Toml } from "@effected/toml";
|
|
210
|
+
* import { Result } from "effect";
|
|
211
|
+
*
|
|
212
|
+
* const ok = Toml.parseResult('name = "Alice"');
|
|
213
|
+
* if (Result.isSuccess(ok)) {
|
|
214
|
+
* console.log(ok.success); // => { name: "Alice" }
|
|
215
|
+
* }
|
|
216
|
+
*
|
|
217
|
+
* const bad = Toml.parseResult("name = ");
|
|
218
|
+
* if (Result.isFailure(bad)) {
|
|
219
|
+
* console.log(bad.failure._tag); // => "TomlParseError"
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @param text - The TOML source to parse.
|
|
224
|
+
* @returns A `Result` succeeding with the decoded value (`unknown`, never
|
|
225
|
+
* `any`), or failing with {@link TomlParseError}.
|
|
226
|
+
*/
|
|
227
|
+
static parseResult(text: string): Result.Result<unknown, TomlParseError>;
|
|
228
|
+
/**
|
|
229
|
+
* Parse a TOML 1.1.0 document into a plain JavaScript value. Defined in
|
|
230
|
+
* terms of {@link Toml.parseResult} — synchronous callers can use that
|
|
231
|
+
* variant directly.
|
|
232
|
+
*
|
|
233
|
+
* @param text - The TOML source to parse.
|
|
234
|
+
* @returns An `Effect` that succeeds with the decoded value, or fails with
|
|
235
|
+
* {@link TomlParseError}.
|
|
176
236
|
*/
|
|
177
237
|
static readonly parse: (text: string) => Effect.Effect<unknown, TomlParseError, never>;
|
|
178
238
|
/**
|
|
179
|
-
* Stringify a plain JavaScript value as a canonical TOML document
|
|
180
|
-
*
|
|
239
|
+
* Stringify a plain JavaScript value as a canonical TOML document,
|
|
240
|
+
* synchronously, returning a `Result` instead of an `Effect`: within a
|
|
241
|
+
* table, non-table pairs first, then sub-tables as `[dotted.header]`
|
|
181
242
|
* sections depth-first, then arrays of tables as `[[dotted.header]]`
|
|
182
243
|
* sections, a blank line before every header except at document start.
|
|
183
244
|
* Fails with {@link TomlStringifyError} on unsupported values (TOML has no
|
|
184
245
|
* null), out-of-int64-range `bigint`s, circular references and
|
|
185
246
|
* depth-guard trips — all on the typed channel.
|
|
247
|
+
*
|
|
248
|
+
* @remarks
|
|
249
|
+
* {@link Toml.stringify} is defined in terms of this function; the two
|
|
250
|
+
* never diverge. Reach for the `Effect` variant inside Effect code — it
|
|
251
|
+
* carries the `Toml.stringify` tracing span — and for this one at
|
|
252
|
+
* synchronous boundaries.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* import { Toml } from "@effected/toml";
|
|
257
|
+
* import { Result } from "effect";
|
|
258
|
+
*
|
|
259
|
+
* const ok = Toml.stringifyResult({ name: "Alice" });
|
|
260
|
+
* if (Result.isSuccess(ok)) {
|
|
261
|
+
* console.log(ok.success); // => 'name = "Alice"\n'
|
|
262
|
+
* }
|
|
263
|
+
*
|
|
264
|
+
* const bad = Toml.stringifyResult({ nope: null });
|
|
265
|
+
* if (Result.isFailure(bad)) {
|
|
266
|
+
* console.log(bad.failure._tag); // => "TomlStringifyError"
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* @param value - The plain JavaScript value to stringify.
|
|
271
|
+
* @param options - Optional {@link TomlStringifyOptions}; `newline`
|
|
272
|
+
* defaults to `"\n"`.
|
|
273
|
+
* @returns A `Result` succeeding with the TOML text, or failing with
|
|
274
|
+
* {@link TomlStringifyError}.
|
|
275
|
+
*/
|
|
276
|
+
static stringifyResult(value: unknown, options?: TomlStringifyOptions): Result.Result<string, TomlStringifyError>;
|
|
277
|
+
/**
|
|
278
|
+
* Stringify a plain JavaScript value as a canonical TOML document. Defined
|
|
279
|
+
* in terms of {@link Toml.stringifyResult} — synchronous callers can use
|
|
280
|
+
* that variant directly.
|
|
281
|
+
*
|
|
282
|
+
* @param value - The plain JavaScript value to stringify.
|
|
283
|
+
* @param options - Optional {@link TomlStringifyOptions}; `newline`
|
|
284
|
+
* defaults to `"\n"`.
|
|
285
|
+
* @returns An `Effect` that succeeds with the TOML text, or fails with
|
|
286
|
+
* {@link TomlStringifyError}.
|
|
186
287
|
*/
|
|
187
288
|
static readonly stringify: (value: unknown, options?: TomlStringifyOptions | undefined) => Effect.Effect<string, TomlStringifyError, never>;
|
|
188
289
|
/**
|
|
@@ -208,6 +309,42 @@ declare class Toml {
|
|
|
208
309
|
* {@link Toml.fromString}).
|
|
209
310
|
*/
|
|
210
311
|
static schema<T, E, RD = never, RE = never>(target: Schema.Codec<T, E, RD, RE>): Schema.Codec<T, string, RD, RE>;
|
|
312
|
+
/**
|
|
313
|
+
* Bind a target schema to the TOML codec once, yielding the composed
|
|
314
|
+
* schema plus pre-derived `decode`/`encode` directions — the
|
|
315
|
+
* {@link Toml.schema} composition without the generic `Schema` machinery
|
|
316
|
+
* at every use site. Binds the plain form only: TOML 1.1.0 parsing on
|
|
317
|
+
* decode, default stringify options on encode.
|
|
318
|
+
*
|
|
319
|
+
* Both directions fail with `Schema.SchemaError`, exactly as
|
|
320
|
+
* `Schema.decodeEffect`/`Schema.encodeEffect` over {@link Toml.schema}
|
|
321
|
+
* would; the target's decoding/encoding service requirements flow through.
|
|
322
|
+
*
|
|
323
|
+
* @remarks
|
|
324
|
+
* Schema-producing: each call composes a fresh schema and derives both
|
|
325
|
+
* directions from it. Bind the result to a `const` — that single binding is
|
|
326
|
+
* the point.
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```ts
|
|
330
|
+
* import { Toml } from "@effected/toml";
|
|
331
|
+
* import { Effect, Schema } from "effect";
|
|
332
|
+
*
|
|
333
|
+
* const Config = Schema.Struct({ name: Schema.String });
|
|
334
|
+
* const config = Toml.bind(Config);
|
|
335
|
+
*
|
|
336
|
+
* const program = Effect.gen(function* () {
|
|
337
|
+
* const value = yield* config.decode('name = "Alice"');
|
|
338
|
+
* const text = yield* config.encode(value);
|
|
339
|
+
* return [value, text] as const;
|
|
340
|
+
* });
|
|
341
|
+
* ```
|
|
342
|
+
*
|
|
343
|
+
* @param target - The domain schema decoded values must satisfy.
|
|
344
|
+
* @returns A {@link TomlBoundCodec} carrying the composed schema and its
|
|
345
|
+
* two pre-bound directions.
|
|
346
|
+
*/
|
|
347
|
+
static bind<T, E, RD = never, RE = never>(target: Schema.Codec<T, E, RD, RE>): TomlBoundCodec<T, RD, RE>;
|
|
211
348
|
}
|
|
212
349
|
//#endregion
|
|
213
350
|
//#region src/TomlDateTime.d.ts
|
|
@@ -385,7 +522,7 @@ declare const TomlArray_base: Schema.Class<TomlArray, Schema.TaggedStruct<"TomlA
|
|
|
385
522
|
readonly length: Schema.Number;
|
|
386
523
|
}>, {}>;
|
|
387
524
|
/**
|
|
388
|
-
* An array value node. Heterogeneous per TOML
|
|
525
|
+
* An array value node. Heterogeneous per TOML; may span multiple lines
|
|
389
526
|
* (the span covers brackets, inner newlines and inner comments).
|
|
390
527
|
*
|
|
391
528
|
* @public
|
|
@@ -410,7 +547,8 @@ declare const TomlInlineTable_base: Schema.Class<TomlInlineTable, Schema.TaggedS
|
|
|
410
547
|
readonly length: Schema.Number;
|
|
411
548
|
}>, {}>;
|
|
412
549
|
/**
|
|
413
|
-
* An inline table value node (`{ k = v, ... }`).
|
|
550
|
+
* An inline table value node (`{ k = v, ... }`). May span multiple lines
|
|
551
|
+
* since TOML 1.1 (the span covers braces, inner newlines and inner comments).
|
|
414
552
|
*
|
|
415
553
|
* @public
|
|
416
554
|
*/
|
|
@@ -893,5 +1031,5 @@ declare class TomlVisitor {
|
|
|
893
1031
|
static visit(text: string): Stream.Stream<TomlVisitorEvent, TomlParseError>;
|
|
894
1032
|
}
|
|
895
1033
|
//#endregion
|
|
896
|
-
export { Toml, TomlArray, TomlArrayTableHeader, TomlBoolean, TomlDateTimeLiteral, TomlDiagnostic, TomlDocument, TomlEdit, TomlErrorCode, TomlExpression, TomlFloat, TomlFormat, TomlFormattingOptions, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlLexErrorCode, TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlModificationError, TomlOffsetDateTime, TomlParseError, TomlParseErrorCode, type TomlPath, TomlRange, type TomlRangeLike, type TomlSegment, TomlSemanticErrorCode, TomlString, TomlStringStyle, TomlStringifyError, TomlStringifyErrorCode, TomlStringifyOptions, TomlTableHeader, TomlTrivia, TomlValueNode, TomlVisitor, TomlVisitorEvent };
|
|
1034
|
+
export { Toml, TomlArray, TomlArrayTableHeader, TomlBoolean, type TomlBoundCodec, TomlDateTimeLiteral, TomlDiagnostic, TomlDocument, TomlEdit, TomlErrorCode, TomlExpression, TomlFloat, TomlFormat, TomlFormattingOptions, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlLexErrorCode, TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlModificationError, TomlOffsetDateTime, TomlParseError, TomlParseErrorCode, type TomlPath, TomlRange, type TomlRangeLike, type TomlSegment, TomlSemanticErrorCode, TomlString, TomlStringStyle, TomlStringifyError, TomlStringifyErrorCode, TomlStringifyOptions, TomlTableHeader, TomlTrivia, TomlValueNode, TomlVisitor, TomlVisitorEvent };
|
|
897
1035
|
//# sourceMappingURL=index.d.ts.map
|
package/internal/diagnostics.js
CHANGED
package/internal/parser.js
CHANGED
|
@@ -105,8 +105,12 @@ const parseLineEnd = (source, pos) => {
|
|
|
105
105
|
end: after
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
|
-
/**
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Skip whitespace, newlines and comments inside array brackets or inline-table
|
|
110
|
+
* braces — the grammar's `ws-comment-newline`, shared by both container
|
|
111
|
+
* parsers since TOML 1.1 let inline tables span lines.
|
|
112
|
+
*/
|
|
113
|
+
const skipValueGap = (source, pos) => {
|
|
110
114
|
let i = pos;
|
|
111
115
|
for (;;) {
|
|
112
116
|
i = scanWhitespace(source, i);
|
|
@@ -122,12 +126,6 @@ const skipArrayGap = (source, pos) => {
|
|
|
122
126
|
return i;
|
|
123
127
|
}
|
|
124
128
|
};
|
|
125
|
-
/** Guard the inline-table single-line rule and unterminated end-of-input. */
|
|
126
|
-
const checkInsideInlineTable = (source, pos, openPos) => {
|
|
127
|
-
if (pos >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, pos - openPos);
|
|
128
|
-
const code = source.charCodeAt(pos);
|
|
129
|
-
if (code === LF || code === CR) raise("NewlineInInlineTable", "inline tables must fit on a single line", pos, 1);
|
|
130
|
-
};
|
|
131
129
|
/** Whether a token that classified as `number` spells a float (never called on hex/oct/bin). */
|
|
132
130
|
const isFloatToken = (token) => !/^0[xob]/.test(token) && (/[.eE]/.test(token) || token.includes("inf") || token.includes("nan"));
|
|
133
131
|
/** An array value starting at `[`; `depth` is this array's own nesting count. */
|
|
@@ -136,7 +134,7 @@ const parseArray = (source, openPos, depth) => {
|
|
|
136
134
|
const items = [];
|
|
137
135
|
let i = openPos + 1;
|
|
138
136
|
for (;;) {
|
|
139
|
-
i =
|
|
137
|
+
i = skipValueGap(source, i);
|
|
140
138
|
if (i >= source.length) raise("UnterminatedArray", "array not closed before end of input", openPos, i - openPos);
|
|
141
139
|
if (source.charCodeAt(i) === RIGHT_BRACKET) {
|
|
142
140
|
i += 1;
|
|
@@ -144,7 +142,7 @@ const parseArray = (source, openPos, depth) => {
|
|
|
144
142
|
}
|
|
145
143
|
const item = parseValue(source, i, depth);
|
|
146
144
|
items.push(item.node);
|
|
147
|
-
i =
|
|
145
|
+
i = skipValueGap(source, item.end);
|
|
148
146
|
const code = source.charCodeAt(i);
|
|
149
147
|
if (code === COMMA) {
|
|
150
148
|
i += 1;
|
|
@@ -166,28 +164,29 @@ const parseArray = (source, openPos, depth) => {
|
|
|
166
164
|
end: i
|
|
167
165
|
};
|
|
168
166
|
};
|
|
169
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* An inline table starting at `{`; `depth` is this table's own nesting count.
|
|
169
|
+
* TOML 1.1 grammar: `ws-comment-newline` may sit before each key-value,
|
|
170
|
+
* before each separator and before the closing brace, and a trailing comma is
|
|
171
|
+
* allowed — but `keyval-sep` is still ws-only, so no newline may split a key
|
|
172
|
+
* from its `=` or the `=` from its value.
|
|
173
|
+
*/
|
|
170
174
|
const parseInlineTable = (source, openPos, depth) => {
|
|
171
175
|
if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, openPos);
|
|
172
176
|
const entries = [];
|
|
173
|
-
let i =
|
|
174
|
-
checkInsideInlineTable(source, i, openPos);
|
|
175
|
-
if (source.charCodeAt(i) === RIGHT_BRACE) return {
|
|
176
|
-
node: new TomlInlineTable({
|
|
177
|
-
entries,
|
|
178
|
-
offset: openPos,
|
|
179
|
-
length: i + 1 - openPos
|
|
180
|
-
}),
|
|
181
|
-
end: i + 1
|
|
182
|
-
};
|
|
177
|
+
let i = openPos + 1;
|
|
183
178
|
for (;;) {
|
|
179
|
+
i = skipValueGap(source, i);
|
|
180
|
+
if (i >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, i - openPos);
|
|
181
|
+
if (source.charCodeAt(i) === RIGHT_BRACE) {
|
|
182
|
+
i += 1;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
184
185
|
const entryStart = i;
|
|
185
186
|
const keyPath = parseKeyPath(source, i);
|
|
186
187
|
i = scanWhitespace(source, keyPath.end);
|
|
187
|
-
checkInsideInlineTable(source, i, openPos);
|
|
188
188
|
if (source.charCodeAt(i) !== EQUALS) raise("ExpectedEquals", "expected = after key", i, 1);
|
|
189
189
|
i = scanWhitespace(source, i + 1);
|
|
190
|
-
checkInsideInlineTable(source, i, openPos);
|
|
191
190
|
const value = parseValue(source, i, depth);
|
|
192
191
|
entries.push(new TomlInlineEntry({
|
|
193
192
|
keyPath: keyPath.node,
|
|
@@ -195,20 +194,17 @@ const parseInlineTable = (source, openPos, depth) => {
|
|
|
195
194
|
offset: entryStart,
|
|
196
195
|
length: value.end - entryStart
|
|
197
196
|
}));
|
|
198
|
-
i =
|
|
199
|
-
checkInsideInlineTable(source, i, openPos);
|
|
197
|
+
i = skipValueGap(source, value.end);
|
|
200
198
|
const code = source.charCodeAt(i);
|
|
201
199
|
if (code === RIGHT_BRACE) {
|
|
202
200
|
i += 1;
|
|
203
201
|
break;
|
|
204
202
|
}
|
|
205
203
|
if (code === COMMA) {
|
|
206
|
-
|
|
207
|
-
i = scanWhitespace(source, i + 1);
|
|
208
|
-
checkInsideInlineTable(source, i, openPos);
|
|
209
|
-
if (source.charCodeAt(i) === RIGHT_BRACE) raise("TrailingCommaInInlineTable", "inline tables may not end with a trailing comma", commaPos, 1);
|
|
204
|
+
i += 1;
|
|
210
205
|
continue;
|
|
211
206
|
}
|
|
207
|
+
if (i >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, i - openPos);
|
|
212
208
|
raise("UnterminatedInlineTable", "expected , or } in inline table", i, 1);
|
|
213
209
|
}
|
|
214
210
|
return {
|
package/internal/scanner.js
CHANGED
|
@@ -17,6 +17,7 @@ const RIGHT_BRACKET = 93;
|
|
|
17
17
|
const RIGHT_BRACE = 125;
|
|
18
18
|
const LOWER_U = 117;
|
|
19
19
|
const UPPER_U = 85;
|
|
20
|
+
const LOWER_X = 120;
|
|
20
21
|
const BOM = 65279;
|
|
21
22
|
const raise = (code, message, offset, length) => {
|
|
22
23
|
throw new RawTomlError({
|
|
@@ -36,7 +37,7 @@ const isBareKeyChar = (code) => code >= 97 && code <= 122 || code >= 65 && code
|
|
|
36
37
|
/** The number of code units to skip for a single leading U+FEFF BOM. */
|
|
37
38
|
const skipBom = (source) => source.charCodeAt(0) === BOM ? 1 : 0;
|
|
38
39
|
/**
|
|
39
|
-
* Reject a document carrying U+FFFD REPLACEMENT CHARACTER. TOML
|
|
40
|
+
* Reject a document carrying U+FFFD REPLACEMENT CHARACTER. TOML requires
|
|
40
41
|
* a valid UTF-8 document, but this engine receives an already-decoded JS
|
|
41
42
|
* string — after Node's lossy utf8 decode, U+FFFD is the only surviving
|
|
42
43
|
* evidence of a malformed byte sequence. The grammar technically admits
|
|
@@ -106,20 +107,29 @@ const simpleEscape = (code) => {
|
|
|
106
107
|
case 110: return "\n";
|
|
107
108
|
case 102: return "\f";
|
|
108
109
|
case 114: return "\r";
|
|
110
|
+
case 101: return "\x1B";
|
|
109
111
|
case QUOTE: return "\"";
|
|
110
112
|
case BACKSLASH: return "\\";
|
|
111
113
|
default: return;
|
|
112
114
|
}
|
|
113
115
|
};
|
|
114
116
|
const HEX_DIGITS = /^[0-9A-Fa-f]+$/;
|
|
117
|
+
const ESCAPE_LETTERS = {
|
|
118
|
+
2: "x",
|
|
119
|
+
4: "u",
|
|
120
|
+
8: "U"
|
|
121
|
+
};
|
|
115
122
|
/**
|
|
116
|
-
* Decode a `\uXXXX` / `\UXXXXXXXX` escape at `backslash`. The code
|
|
117
|
-
* be a Unicode scalar value: surrogates and values above U+10FFFF
|
|
123
|
+
* Decode a `\xHH` / `\uXXXX` / `\UXXXXXXXX` escape at `backslash`. The code
|
|
124
|
+
* point must be a Unicode scalar value: surrogates and values above U+10FFFF
|
|
125
|
+
* are errors. `\xHH` (TOML 1.1) covers U+0000–U+00FF — the escape is a
|
|
126
|
+
* grammar-sibling of `basic-unescaped`, so the decoded character bypasses the
|
|
127
|
+
* control-character ban by design.
|
|
118
128
|
*/
|
|
119
129
|
const decodeUnicodeEscape = (source, backslash, width) => {
|
|
120
130
|
const start = backslash + 2;
|
|
121
131
|
const hex = source.slice(start, start + width);
|
|
122
|
-
if (hex.length < width || !HEX_DIGITS.test(hex)) return raise("InvalidUnicodeEscape", `\\${width
|
|
132
|
+
if (hex.length < width || !HEX_DIGITS.test(hex)) return raise("InvalidUnicodeEscape", `\\${ESCAPE_LETTERS[width]} escape requires ${width} hexadecimal digits`, backslash, width + 2);
|
|
123
133
|
const codePoint = Number.parseInt(hex, 16);
|
|
124
134
|
if (codePoint >= 55296 && codePoint <= 57343 || codePoint > 1114111) return raise("InvalidUnicodeEscape", `${codeLabel(codePoint)} is not a Unicode scalar value`, backslash, width + 2);
|
|
125
135
|
return {
|
|
@@ -127,6 +137,8 @@ const decodeUnicodeEscape = (source, backslash, width) => {
|
|
|
127
137
|
end: start + width
|
|
128
138
|
};
|
|
129
139
|
};
|
|
140
|
+
/** The escape width for a unicode-style escape letter, or undefined. */
|
|
141
|
+
const unicodeEscapeWidth = (code) => code === LOWER_X ? 2 : code === LOWER_U ? 4 : code === UPPER_U ? 8 : void 0;
|
|
130
142
|
/** Scan a single-line basic string starting at the opening `"`. */
|
|
131
143
|
const scanBasicString = (source, pos) => {
|
|
132
144
|
let out = "";
|
|
@@ -143,11 +155,12 @@ const scanBasicString = (source, pos) => {
|
|
|
143
155
|
out += source.slice(chunkStart, i);
|
|
144
156
|
const next = source.charCodeAt(i + 1);
|
|
145
157
|
const mapped = simpleEscape(next);
|
|
158
|
+
const width = unicodeEscapeWidth(next);
|
|
146
159
|
if (mapped !== void 0) {
|
|
147
160
|
out += mapped;
|
|
148
161
|
i += 2;
|
|
149
|
-
} else if (
|
|
150
|
-
const decoded = decodeUnicodeEscape(source, i,
|
|
162
|
+
} else if (width !== void 0) {
|
|
163
|
+
const decoded = decodeUnicodeEscape(source, i, width);
|
|
151
164
|
out += String.fromCodePoint(decoded.codePoint);
|
|
152
165
|
i = decoded.end;
|
|
153
166
|
} else return raise("InvalidEscape", `invalid escape sequence \\${source[i + 1] ?? ""}`, i, 2);
|
|
@@ -234,8 +247,9 @@ const scanMultilineBasicString = (source, pos) => {
|
|
|
234
247
|
chunkStart = i;
|
|
235
248
|
continue;
|
|
236
249
|
}
|
|
237
|
-
|
|
238
|
-
|
|
250
|
+
const width = unicodeEscapeWidth(next);
|
|
251
|
+
if (width !== void 0) {
|
|
252
|
+
const decoded = decodeUnicodeEscape(source, i, width);
|
|
239
253
|
out += String.fromCodePoint(decoded.codePoint);
|
|
240
254
|
i = decoded.end;
|
|
241
255
|
chunkStart = i;
|
|
@@ -319,10 +333,10 @@ const scanTokenSpan = (source, pos) => {
|
|
|
319
333
|
}
|
|
320
334
|
return i;
|
|
321
335
|
};
|
|
322
|
-
const OFFSET_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})
|
|
323
|
-
const LOCAL_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})
|
|
336
|
+
const OFFSET_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?([Zz]|[+-][0-9]{2}:[0-9]{2})$/;
|
|
337
|
+
const LOCAL_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?$/;
|
|
324
338
|
const LOCAL_DATE = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
|
|
325
|
-
const LOCAL_TIME = /^([0-9]{2}):([0-9]{2})
|
|
339
|
+
const LOCAL_TIME = /^([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?$/;
|
|
326
340
|
/**
|
|
327
341
|
* Scan a non-string scalar value token. Stops at whitespace, newlines, `,`,
|
|
328
342
|
* `]`, `}` and `#` — with the G5 extension: a token that scanned as a full
|
|
@@ -409,13 +423,13 @@ const classifyValueToken = (token, offset) => {
|
|
|
409
423
|
const length = Math.max(token.length, 1);
|
|
410
424
|
let match = OFFSET_DATE_TIME.exec(token);
|
|
411
425
|
if (match !== null) {
|
|
412
|
-
const [, y = "", mo = "", d = "", h = "", mi = "", s
|
|
426
|
+
const [, y = "", mo = "", d = "", h = "", mi = "", s, fraction, offsetText = ""] = match;
|
|
413
427
|
const year = Number(y);
|
|
414
428
|
const month = Number(mo);
|
|
415
429
|
const day = Number(d);
|
|
416
430
|
const hour = Number(h);
|
|
417
431
|
const minute = Number(mi);
|
|
418
|
-
const second = Number(s);
|
|
432
|
+
const second = s === void 0 ? 0 : Number(s);
|
|
419
433
|
validateDate(year, month, day, offset, length);
|
|
420
434
|
validateTime(hour, minute, second, offset, length);
|
|
421
435
|
const offsetMinutes = decodeOffsetMinutes(offsetText, offset, length);
|
|
@@ -432,13 +446,13 @@ const classifyValueToken = (token, offset) => {
|
|
|
432
446
|
}
|
|
433
447
|
match = LOCAL_DATE_TIME.exec(token);
|
|
434
448
|
if (match !== null) {
|
|
435
|
-
const [, y = "", mo = "", d = "", h = "", mi = "", s
|
|
449
|
+
const [, y = "", mo = "", d = "", h = "", mi = "", s, fraction] = match;
|
|
436
450
|
const year = Number(y);
|
|
437
451
|
const month = Number(mo);
|
|
438
452
|
const day = Number(d);
|
|
439
453
|
const hour = Number(h);
|
|
440
454
|
const minute = Number(mi);
|
|
441
|
-
const second = Number(s);
|
|
455
|
+
const second = s === void 0 ? 0 : Number(s);
|
|
442
456
|
validateDate(year, month, day, offset, length);
|
|
443
457
|
validateTime(hour, minute, second, offset, length);
|
|
444
458
|
return new TomlLocalDateTime({
|
|
@@ -466,10 +480,10 @@ const classifyValueToken = (token, offset) => {
|
|
|
466
480
|
}
|
|
467
481
|
match = LOCAL_TIME.exec(token);
|
|
468
482
|
if (match !== null) {
|
|
469
|
-
const [, h = "", mi = "", s
|
|
483
|
+
const [, h = "", mi = "", s, fraction] = match;
|
|
470
484
|
const hour = Number(h);
|
|
471
485
|
const minute = Number(mi);
|
|
472
|
-
const second = Number(s);
|
|
486
|
+
const second = s === void 0 ? 0 : Number(s);
|
|
473
487
|
validateTime(hour, minute, second, offset, length);
|
|
474
488
|
return new TomlLocalTime({
|
|
475
489
|
hour,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/toml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "TOML 1.
|
|
5
|
+
"description": "TOML 1.1.0 parsing, editing and formatting as Effect schemas: typed diagnostics, a lossless CST and first-class date-time values.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"toml",
|
|
8
8
|
"parser",
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
"exports": {
|
|
33
33
|
".": {
|
|
34
34
|
"types": "./index.d.ts",
|
|
35
|
-
"import": "./index.js"
|
|
35
|
+
"import": "./index.js",
|
|
36
|
+
"default": "./index.js"
|
|
36
37
|
},
|
|
37
38
|
"./package.json": "./package.json"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
|
-
"effect": "4.0.0-beta.
|
|
41
|
+
"effect": "4.0.0-beta.99"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=24.11.0"
|