@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/TomlDocument.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { isRawTomlError } from "./internal/diagnostics.js";
|
|
2
|
+
import { isGuardExceeded } from "./internal/limits.js";
|
|
3
|
+
import { TomlExpression } from "./TomlNode.js";
|
|
4
|
+
import { parseExpressions } from "./internal/parser.js";
|
|
5
|
+
import { analyze, buildValue } from "./internal/semantic.js";
|
|
6
|
+
import { TomlDiagnostic } from "./TomlDiagnostic.js";
|
|
7
|
+
import { TomlParseError } from "./Toml.js";
|
|
8
|
+
import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
9
|
+
|
|
10
|
+
//#region src/TomlDocument.ts
|
|
11
|
+
/**
|
|
12
|
+
* Materialize an engine throw into the typed error: `RawTomlError` becomes a
|
|
13
|
+
* positioned diagnostic and a `GuardExceeded` depth trip becomes a
|
|
14
|
+
* `NestingDepthExceeded` diagnostic. Anything else is a genuine defect and
|
|
15
|
+
* rethrows.
|
|
16
|
+
*/
|
|
17
|
+
const materializeError = (text, defect) => {
|
|
18
|
+
if (isRawTomlError(defect)) return new TomlParseError({ diagnostics: [TomlDiagnostic.fromRaw(text, defect.diagnostic)] });
|
|
19
|
+
if (isGuardExceeded(defect)) return new TomlParseError({ diagnostics: [TomlDiagnostic.fromRaw(text, {
|
|
20
|
+
code: "NestingDepthExceeded",
|
|
21
|
+
message: defect.message,
|
|
22
|
+
offset: defect.offset,
|
|
23
|
+
length: 0
|
|
24
|
+
})] });
|
|
25
|
+
throw defect;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A parsed TOML document that never loses a byte: the `source` text, the
|
|
29
|
+
* linear {@link (TomlExpression:type)} CST whose spans tile the source
|
|
30
|
+
* exactly, and
|
|
31
|
+
* any semantic violations as {@link TomlDiagnostic} data.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* `parse` fails typed only on lex/parse errors; a syntactically valid but
|
|
35
|
+
* semantically illegal document (say, a duplicate key) still parses with the
|
|
36
|
+
* violation recorded in `diagnostics`, so the text stays inspectable and
|
|
37
|
+
* editable. `stringify` reconstructs the source by concatenating expression
|
|
38
|
+
* spans — that the result equals `source` is the span-bookkeeping proof, held
|
|
39
|
+
* byte-exact across the full toml-test corpus. `toValue` refuses on a
|
|
40
|
+
* non-empty `diagnostics`.
|
|
41
|
+
*
|
|
42
|
+
* Construct via {@link TomlDocument.parse}; `TomlDocument.make` is for
|
|
43
|
+
* synthetic documents.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { TomlDocument } from "@effected/toml";
|
|
48
|
+
* import { Effect } from "effect";
|
|
49
|
+
*
|
|
50
|
+
* const program = Effect.gen(function* () {
|
|
51
|
+
* const doc = yield* TomlDocument.parse('name = "Alice"\n');
|
|
52
|
+
* doc.stringify(); // 'name = "Alice"\n' — byte-exact
|
|
53
|
+
* return yield* doc.toValue(); // { name: "Alice" }
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
var TomlDocument = class TomlDocument extends Schema.Class("TomlDocument")({
|
|
60
|
+
source: Schema.String,
|
|
61
|
+
expressions: Schema.Array(TomlExpression),
|
|
62
|
+
diagnostics: Schema.Array(TomlDiagnostic)
|
|
63
|
+
}) {
|
|
64
|
+
/**
|
|
65
|
+
* Parse TOML text into a lossless document. Fails with
|
|
66
|
+
* {@link TomlParseError} only on lex/parse errors — including a
|
|
67
|
+
* nesting-depth bomb, which surfaces as a `NestingDepthExceeded`
|
|
68
|
+
* diagnostic, never an unhandled defect. Semantic violations do not fail:
|
|
69
|
+
* they land in `diagnostics` as data (first violation wins, so there is at
|
|
70
|
+
* most one today; the array shape is the contract).
|
|
71
|
+
*/
|
|
72
|
+
static parse = Effect.fn("TomlDocument.parse")(function* (text) {
|
|
73
|
+
const expressions = yield* Effect.try({
|
|
74
|
+
try: () => parseExpressions(text),
|
|
75
|
+
catch: (defect) => materializeError(text, defect)
|
|
76
|
+
});
|
|
77
|
+
const diagnostics = [];
|
|
78
|
+
try {
|
|
79
|
+
analyze(expressions);
|
|
80
|
+
} catch (defect) {
|
|
81
|
+
if (!isRawTomlError(defect)) throw defect;
|
|
82
|
+
diagnostics.push(TomlDiagnostic.fromRaw(text, defect.diagnostic));
|
|
83
|
+
}
|
|
84
|
+
return TomlDocument.make({
|
|
85
|
+
source: text,
|
|
86
|
+
expressions,
|
|
87
|
+
diagnostics
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
/**
|
|
91
|
+
* A `Schema<TomlDocument, string>` decoding TOML text into a full document
|
|
92
|
+
* (source, expressions, diagnostics) and encoding a document back to its
|
|
93
|
+
* byte-exact text.
|
|
94
|
+
*
|
|
95
|
+
* Schema-producing: each call returns a fresh schema whose derivation
|
|
96
|
+
* caches are not shared across calls; bind the result to a `const` on hot
|
|
97
|
+
* paths.
|
|
98
|
+
*/
|
|
99
|
+
static schema() {
|
|
100
|
+
return Schema.String.pipe(Schema.decodeTo(Schema.instanceOf(TomlDocument), SchemaTransformation.transformOrFail({
|
|
101
|
+
decode: (input) => TomlDocument.parse(input).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue(Option.some(input), { message: error.message }))),
|
|
102
|
+
encode: (doc) => Effect.succeed(doc.stringify())
|
|
103
|
+
})));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Materialize the document's plain JavaScript value. Fails with
|
|
107
|
+
* {@link TomlParseError} carrying the stored `diagnostics` when the parse
|
|
108
|
+
* recorded semantic violations; otherwise builds the value from the
|
|
109
|
+
* expression list (already validated, so the defensive materialization
|
|
110
|
+
* wrapper is belt-and-suspenders).
|
|
111
|
+
*/
|
|
112
|
+
toValue() {
|
|
113
|
+
if (this.diagnostics.length > 0) return Effect.fail(new TomlParseError({ diagnostics: this.diagnostics }));
|
|
114
|
+
return Effect.try({
|
|
115
|
+
try: () => buildValue(this.expressions),
|
|
116
|
+
catch: (defect) => materializeError(this.source, defect)
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Reconstruct the document text by concatenating each expression's source
|
|
121
|
+
* span in order. The expression spans tile the source exactly, so the
|
|
122
|
+
* result equals `source` byte-for-byte — the round-trip contract this
|
|
123
|
+
* class exists to prove. Pure and total.
|
|
124
|
+
*/
|
|
125
|
+
stringify() {
|
|
126
|
+
let out = "";
|
|
127
|
+
for (const expression of this.expressions) out += this.source.slice(expression.offset, expression.offset + expression.length);
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { TomlDocument };
|
package/TomlEdit.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/TomlEdit.ts
|
|
4
|
+
/**
|
|
5
|
+
* A range within a TOML document, expressed as a zero-based character
|
|
6
|
+
* `offset` and a `length` in UTF-16 code units. Pass to `TomlFormat.format`
|
|
7
|
+
* to restrict formatting to the expressions intersecting a region.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
var TomlRange = class extends Schema.Class("TomlRange")({
|
|
12
|
+
offset: Schema.Number,
|
|
13
|
+
length: Schema.Number
|
|
14
|
+
}) {};
|
|
15
|
+
/**
|
|
16
|
+
* A non-mutating text edit: replace the span `[offset, offset + length)` with
|
|
17
|
+
* `content`. Set `length` to `0` to insert, `content` to `""` to delete.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Structurally identical to `@effected/jsonc`'s and `@effected/yaml`'s edit
|
|
21
|
+
* shapes (same field names, types and semantics) per the jsonc/yaml parity
|
|
22
|
+
* convention, so consumer code can be written once over "a document codec's
|
|
23
|
+
* Edit/Range/Path".
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
var TomlEdit = class extends Schema.Class("TomlEdit")({
|
|
28
|
+
offset: Schema.Number,
|
|
29
|
+
length: Schema.Number,
|
|
30
|
+
content: Schema.String
|
|
31
|
+
}) {
|
|
32
|
+
/**
|
|
33
|
+
* Apply `edits` to `text`, producing a new string. Edits are applied in
|
|
34
|
+
* reverse-offset order so earlier offsets stay valid; the input `edits`
|
|
35
|
+
* array is not mutated. Overlapping edits are a programmer error and throw
|
|
36
|
+
* as a defect — `TomlFormat` never produces them.
|
|
37
|
+
*/
|
|
38
|
+
static applyAll(text, edits) {
|
|
39
|
+
const sorted = [...edits].sort((a, b) => b.offset - a.offset);
|
|
40
|
+
for (let i = 0; i + 1 < sorted.length; i++) {
|
|
41
|
+
const upper = sorted[i];
|
|
42
|
+
const lower = sorted[i + 1];
|
|
43
|
+
if (lower.offset + lower.length > upper.offset) throw new Error(`TomlEdit.applyAll received overlapping edits at offsets ${lower.offset} and ${upper.offset} — overlapping edits are a programmer error`);
|
|
44
|
+
}
|
|
45
|
+
let result = text;
|
|
46
|
+
for (const edit of sorted) result = result.slice(0, edit.offset) + edit.content + result.slice(edit.offset + edit.length);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { TomlEdit, TomlRange };
|