smol-toml 1.0.1 → 1.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![License](https://img.shields.io/github/license/squirrelchat/smol-toml.svg?style=flat-square)](https://github.com/squirrelchat/smol-toml/blob/mistress/LICENSE)
4
4
  [![npm](https://img.shields.io/npm/v/smol-toml?style=flat-square)](https://npm.im/smol-toml)
5
5
 
6
- A small, fast, and correct TOML parser. smol-toml is fully(ish) spec-compliant with TOML v1.0.0.
6
+ A small, fast, and correct TOML parser and serializer. smol-toml is fully(ish) spec-compliant with TOML v1.0.0.
7
7
 
8
8
  Why yet another TOML parser? Well, the ecosystem of TOML parsers in JavaScript is quite underwhelming, most likely due
9
9
  to a lack of interest. With most parsers being outdated, unmaintained, non-compliant, or a combination of these, a new
@@ -23,12 +23,13 @@ smol-toml also passes all of the tests in https://github.com/iarna/toml-spec-tes
23
23
 
24
24
  <details>
25
25
  <summary>List of failed `toml-test` cases</summary>
26
+
26
27
  These tests were done by modifying `primitive.ts` and make the implementation return bigints for integers. This allows
27
28
  verifying the parser correctly intents a number to be an integer or a float.
28
29
 
29
30
  *Ideally, this becomes an option of the library, but for now...*
30
31
 
31
- The following tests are failing:
32
+ The following parse tests are failing:
32
33
  - invalid/encoding/bad-utf8-in-comment
33
34
  - invalid/encoding/bad-utf8-in-multiline-literal
34
35
  - invalid/encoding/bad-utf8-in-multiline
@@ -44,11 +45,65 @@ The following tests are failing:
44
45
 
45
46
  ## Usage
46
47
  ```js
47
- import { parse } from 'smol-toml'
48
+ import { parse, stringify } from 'smol-toml'
48
49
 
49
50
  const doc = '...'
50
51
  const parsed = parse(doc)
51
52
  console.log(parsed)
53
+
54
+ const toml = stringify(parsed)
55
+ console.log(toml)
56
+ ```
57
+
58
+ A few notes on the `stringify` function:
59
+ - `undefined` and `null` values on objects are ignored (does not produce a key/value).
60
+ - `undefined` and `null` values in arrays are **rejected**.
61
+ - Functions, classes and symbols are **rejected**.
62
+ - floats will be serialized as integers if they don't have a decimal part.
63
+ - `stringify(parse('a = 1.0')) === 'a = 1'`
64
+ - JS `Date` will be serialized as Offset Date Time
65
+ - Use the [`TomlDate` object](#dates) for representing other types.
66
+
67
+ ### Dates
68
+ `smol-toml` uses an extended `Date` object to represent all types of TOML Dates. In the future, `smol-toml` will use
69
+ objects from the Temporal proposal, but for now we're stuck with the legacy Date object.
70
+
71
+ ```js
72
+ import { TomlDate } from 'smol-toml'
73
+
74
+ // Offset Date Time
75
+ const date = new TomlDate('1979-05-27T07:32:00.000-08:00')
76
+ console.log(date.isDateTime(), date.isDate(), date.isTime(), date.isLocal()) // ~> true, false, false, false
77
+ console.log(date.toISOString()) // ~> 1979-05-27T07:32:00.000-08:00
78
+
79
+ // Local Date Time
80
+ const date = new TomlDate('1979-05-27T07:32:00.000')
81
+ console.log(date.isDateTime(), date.isDate(), date.isTime(), date.isLocal()) // ~> true, false, false, true
82
+ console.log(date.toISOString()) // ~> 1979-05-27T07:32:00.000
83
+
84
+ // Local Date
85
+ const date = new TomlDate('1979-05-27')
86
+ console.log(date.isDateTime(), date.isDate(), date.isTime(), date.isLocal()) // ~> false, true, false, true
87
+ console.log(date.toISOString()) // ~> 1979-05-27
88
+
89
+ // Local Time
90
+ const date = new TomlDate('07:32:00')
91
+ console.log(date.isDateTime(), date.isDate(), date.isTime(), date.isLocal()) // ~> false, false, true, true
92
+ console.log(date.toISOString()) // ~> 07:32:00.000
93
+ ```
94
+
95
+ You can also wrap a native `Date` object and specify using different methods depending on the type of date you wish
96
+ to represent:
97
+
98
+ ```js
99
+ import { TomlDate } from 'smol-toml'
100
+
101
+ const jsDate = new Date()
102
+
103
+ const offsetDateTime = TomlDate.wrapAsOffsetDateTime(jsDate)
104
+ const localDateTime = TomlDate.wrapAsLocalDateTime(jsDate)
105
+ const localDate = TomlDate.wrapAsLocalDate(jsDate)
106
+ const localTime = TomlDate.wrapAsLocalTime(jsDate)
52
107
  ```
53
108
 
54
109
  ## Performance
@@ -61,15 +116,20 @@ idea is to have a file relatively close to a real-world application.
61
116
 
62
117
  The large TOML generator can be found [here](https://gist.github.com/cyyynthia/e77c744cb6494dabe37d0182506526b9)
63
118
 
64
- | | smol-toml | @iarna/toml@3.0.0 | @ltd/j-toml | fast-toml |
65
- |----------------|---------------------|-------------------|----------------|----------------|
66
- | Spec example | **71,356.51 op/s** | 33,629.31 op/s | 16,433.86 op/s | 29,421.60 op/s |
67
- | ~5MB test file | **3.8091 op/s** | *DNF* | 2.4369 op/s | 2.6078 op/s |
119
+ | **Parse** | smol-toml | @iarna/toml@3.0.0 | @ltd/j-toml | fast-toml |
120
+ |----------------|---------------------|-------------------|-----------------|-----------------|
121
+ | Spec example | **71,356.51 op/s** | 33,629.31 op/s | 16,433.86 op/s | 29,421.60 op/s |
122
+ | ~5MB test file | **3.8091 op/s** | *DNF* | 2.4369 op/s | 2.6078 op/s |
123
+
124
+ | **Stringify** | smol-toml | @iarna/toml@3.0.0 | @ltd/j-toml |
125
+ |----------------|----------------------|-------------------|----------------|
126
+ | Spec example | **195,191.99 op/s** | 46,583.07 op/s | 5,670.12 op/s |
127
+ | ~5MB test file | **14.6709 op/s** | 3.5941 op/s | 0.7856 op/s |
68
128
 
69
129
  <details>
70
130
  <summary>Detailed benchmark data</summary>
71
131
 
72
- Tests ran using Vitest v0.31.0 on commit 04d233e351f9ae719222154ee2217aea8b95dbab
132
+ Tests ran using Vitest v0.31.0 on commit f58cb6152e667e9cea09f31c93d90652e3b82bf5
73
133
 
74
134
  CPU: Intel Core i7 7700K (4.2GHz)
75
135
 
@@ -87,6 +147,16 @@ CPU: Intel Core i7 7700K (4.2GHz)
87
147
  · smol-toml 3.8091 239.60 287.30 262.53 274.17 287.30 287.30 287.30 ±3.66% 10 fastest
88
148
  · @ltd/j-toml 2.4369 376.73 493.49 410.35 442.58 493.49 493.49 493.49 ±7.08% 10 slowest
89
149
  · fast-toml 2.6078 373.88 412.79 383.47 388.62 412.79 412.79 412.79 ±2.72% 10
150
+ ✓ bench/stringifySpecExample.bench.ts (3) 1886ms
151
+ name hz min max mean p75 p99 p995 p999 rme samples
152
+ · smol-toml 195,191.99 0.0047 0.2704 0.0051 0.0050 0.0099 0.0110 0.0152 ±0.41% 97596 fastest
153
+ · @iarna/toml 46,583.07 0.0197 0.2808 0.0215 0.0208 0.0448 0.0470 0.1704 ±0.47% 23292
154
+ · @ltd/j-toml 5,670.12 0.1613 0.5768 0.1764 0.1726 0.3036 0.3129 0.4324 ±0.56% 2836 slowest
155
+ ✓ bench/stringifyLargeMixed.bench.ts (3) 24057ms
156
+ name hz min max mean p75 p99 p995 p999 rme samples
157
+ · smol-toml 14.6709 65.1071 79.2199 68.1623 67.1088 79.2199 79.2199 79.2199 ±5.25% 10 fastest
158
+ · @iarna/toml 3.5941 266.48 295.24 278.24 290.10 295.24 295.24 295.24 ±2.83% 10
159
+ · @ltd/j-toml 0.7856 1,254.33 1,322.05 1,272.87 1,286.82 1,322.05 1,322.05 1,322.05 ±1.37% 10 slowest
90
160
 
91
161
 
92
162
  BENCH Summary
@@ -99,6 +169,14 @@ CPU: Intel Core i7 7700K (4.2GHz)
99
169
  2.12x faster than @iarna/toml
100
170
  2.43x faster than fast-toml
101
171
  4.34x faster than @ltd/j-toml
172
+
173
+ smol-toml - bench/stringifyLargeMixed.bench.ts >
174
+ 4.00x faster than @iarna/toml
175
+ 18.33x faster than @ltd/j-toml
176
+
177
+ smol-toml - bench/stringifySpecExample.bench.ts >
178
+ 4.19x faster than @iarna/toml
179
+ 34.42x faster than @ltd/j-toml
102
180
  ```
103
181
 
104
182
  ---
@@ -111,7 +189,7 @@ I initially reported this to the library author, but the author decided to
111
189
  - b) [delete the issue](https://github.com/huan231/toml-nodejs/issues/12) when pointed out links to the NodeJS
112
190
  documentation about the flag removal and standard resolution algorithm.
113
191
 
114
- For the reference anyways, `toml-nodejs` (with proper imports) is ~8x slower on both benchmark with:
192
+ For the reference anyways, `toml-nodejs` (with proper imports) is ~8x slower on both parse benchmark with:
115
193
  - spec example: 7,543.47 op/s
116
194
  - 5mb mixed: 0.7006 op/s
117
195
  </details>
package/dist/date.js CHANGED
@@ -49,6 +49,8 @@ export default class TomlDate extends Date {
49
49
  else {
50
50
  offset = match[3] || null;
51
51
  date = date.toUpperCase();
52
+ if (!offset)
53
+ date += 'Z';
52
54
  }
53
55
  }
54
56
  else {
@@ -0,0 +1,29 @@
1
+ /*!
2
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright notice, this
9
+ * list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ * this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of the copyright holder nor the names of its contributors
14
+ * may be used to endorse or promote products derived from this software without
15
+ * specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+ import { type TomlPrimitive } from './util.js';
29
+ export declare function extractValue(str: string, ptr: number, end?: string): [TomlPrimitive, number];
@@ -0,0 +1,92 @@
1
+ /*!
2
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright notice, this
9
+ * list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ * this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of the copyright holder nor the names of its contributors
14
+ * may be used to endorse or promote products derived from this software without
15
+ * specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+ import { parseString, parseValue } from './primitive.js';
29
+ import { parseArray, parseInlineTable } from './struct.js';
30
+ import { indexOfNewline, skipVoid, skipUntil, skipComment, getStringEnd } from './util.js';
31
+ import TomlError from './error.js';
32
+ function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
33
+ let value = str.slice(startPtr, endPtr);
34
+ let commentIdx = value.indexOf('#');
35
+ if (commentIdx > -1) {
36
+ // The call to skipComment allows to "validate" the comment
37
+ // (absence of control characters)
38
+ skipComment(str, commentIdx);
39
+ value = value.slice(0, commentIdx);
40
+ }
41
+ let trimmed = value.trimEnd();
42
+ if (!allowNewLines) {
43
+ let newlineIdx = value.indexOf('\n', trimmed.length);
44
+ if (newlineIdx > -1) {
45
+ throw new TomlError('newlines are not allowed in inline tables', {
46
+ toml: str,
47
+ ptr: startPtr + newlineIdx
48
+ });
49
+ }
50
+ }
51
+ return [trimmed, commentIdx];
52
+ }
53
+ export function extractValue(str, ptr, end) {
54
+ let c = str[ptr];
55
+ if (c === '[' || c === '{') {
56
+ let [value, endPtr] = c === '['
57
+ ? parseArray(str, ptr)
58
+ : parseInlineTable(str, ptr);
59
+ let newPtr = skipUntil(str, endPtr, ',', end);
60
+ if (end === '}') {
61
+ let nextNewLine = indexOfNewline(str, endPtr, newPtr);
62
+ if (nextNewLine > -1) {
63
+ throw new TomlError('newlines are not allowed in inline tables', {
64
+ toml: str,
65
+ ptr: nextNewLine
66
+ });
67
+ }
68
+ }
69
+ return [value, newPtr];
70
+ }
71
+ let endPtr;
72
+ if (c === '"' || c === "'") {
73
+ endPtr = getStringEnd(str, ptr);
74
+ return [parseString(str, ptr, endPtr), endPtr + +(!!end && str[endPtr] === ',')];
75
+ }
76
+ endPtr = skipUntil(str, ptr, ',', end);
77
+ let slice = sliceAndTrimEndOf(str, ptr, endPtr - (+(str[endPtr - 1] === ',')), end === ']');
78
+ if (!slice[0]) {
79
+ throw new TomlError('incomplete key-value declaration: no value specified', {
80
+ toml: str,
81
+ ptr: ptr
82
+ });
83
+ }
84
+ if (end && slice[1] > -1) {
85
+ endPtr = skipVoid(str, ptr + slice[1]);
86
+ endPtr += +(str[endPtr] === ',');
87
+ }
88
+ return [
89
+ parseValue(slice[0], str, ptr),
90
+ endPtr,
91
+ ];
92
+ }
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- import { type TomlPrimitive } from './util.js';
28
+ export { default as TomlError } from './error.js';
29
29
  export { default as TomlDate } from './date.js';
30
- export declare function parse(toml: string): Record<string, TomlPrimitive>;
30
+ export { parse } from './parse.js';
31
+ export { stringify } from './stringify.js';
package/dist/index.js CHANGED
@@ -25,125 +25,7 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- import { parseKey } from './struct.js';
29
- import { extractValue } from './parse.js';
30
- import { skipVoid } from './util.js';
31
- import TomlError from './error.js';
28
+ export { default as TomlError } from './error.js';
32
29
  export { default as TomlDate } from './date.js';
33
- function peekTable(key, table, meta, type) {
34
- let t = table;
35
- let m = meta;
36
- let k;
37
- let hasOwn = false;
38
- let state;
39
- for (let i = 0; i < key.length; i++) {
40
- if (i) {
41
- t = hasOwn ? t[k] : (t[k] = {});
42
- m = (state = m[k]).c;
43
- if (type === 0 /* Type.DOTTED */ && state.t === 1 /* Type.EXPLICIT */) {
44
- return null;
45
- }
46
- if (state.t === 2 /* Type.ARRAY */) {
47
- let l = t.length - 1;
48
- t = t[l];
49
- m = m[l].c;
50
- }
51
- }
52
- k = key[i];
53
- if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 /* Type.DOTTED */ && m[k]?.d) {
54
- return null;
55
- }
56
- if (!hasOwn) {
57
- if (k === '__proto__') {
58
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
59
- Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
60
- }
61
- m[k] = {
62
- t: i < key.length - 1 && type === 2 /* Type.ARRAY */
63
- ? 0 /* Type.DOTTED */
64
- : type,
65
- d: false,
66
- i: 0,
67
- c: {},
68
- };
69
- }
70
- }
71
- state = m[k];
72
- if (state.t !== type) {
73
- // Bad key type!
74
- return null;
75
- }
76
- if (type === 2 /* Type.ARRAY */) {
77
- if (!state.d) {
78
- state.d = true;
79
- t[k] = [];
80
- }
81
- t[k].push(t = {});
82
- state.c[state.i++] = (state = { t: 1 /* Type.EXPLICIT */, d: false, i: 0, c: {} });
83
- }
84
- if (state.d) {
85
- // Redefining a table!
86
- return null;
87
- }
88
- state.d = true;
89
- if (type === 1 /* Type.EXPLICIT */) {
90
- t = hasOwn ? t[k] : (t[k] = {});
91
- }
92
- else if (type === 0 /* Type.DOTTED */ && hasOwn) {
93
- return null;
94
- }
95
- return [k, t, state.c];
96
- }
97
- export function parse(toml) {
98
- let res = {};
99
- let meta = {};
100
- let tbl = res;
101
- let m = meta;
102
- for (let ptr = skipVoid(toml, 0); ptr < toml.length;) {
103
- if (toml[ptr] === '[') {
104
- let isTableArray = toml[++ptr] === '[';
105
- let k = parseKey(toml, ptr += +isTableArray, ']');
106
- if (isTableArray) {
107
- if (toml[k[1] - 1] !== ']') {
108
- throw new TomlError('expected end of table declaration', {
109
- toml: toml,
110
- ptr: k[1] - 1,
111
- });
112
- }
113
- k[1]++;
114
- }
115
- let p = peekTable(k[0], res, meta, isTableArray ? 2 /* Type.ARRAY */ : 1 /* Type.EXPLICIT */);
116
- if (!p) {
117
- throw new TomlError('trying to redefine an already defined table or value', {
118
- toml: toml,
119
- ptr: ptr,
120
- });
121
- }
122
- m = p[2];
123
- tbl = p[1];
124
- ptr = k[1];
125
- }
126
- else {
127
- let k = parseKey(toml, ptr);
128
- let p = peekTable(k[0], tbl, m, 0 /* Type.DOTTED */);
129
- if (!p) {
130
- throw new TomlError('trying to redefine an already defined table or value', {
131
- toml: toml,
132
- ptr: ptr,
133
- });
134
- }
135
- let v = extractValue(toml, k[1]);
136
- p[1][p[0]] = v[0];
137
- ptr = v[1];
138
- }
139
- ptr = skipVoid(toml, ptr, true);
140
- if (toml[ptr] && toml[ptr] !== '\n' && toml[ptr] !== '\r') {
141
- throw new TomlError('each key-value declaration must be followed by an end-of-line', {
142
- toml: toml,
143
- ptr: ptr
144
- });
145
- }
146
- ptr = skipVoid(toml, ptr);
147
- }
148
- return res;
149
- }
30
+ export { parse } from './parse.js';
31
+ export { stringify } from './stringify.js';
package/dist/parse.d.ts CHANGED
@@ -26,4 +26,4 @@
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
  import { type TomlPrimitive } from './util.js';
29
- export declare function extractValue(str: string, ptr: number, end?: string): [TomlPrimitive, number];
29
+ export declare function parse(toml: string): Record<string, TomlPrimitive>;
package/dist/parse.js CHANGED
@@ -25,68 +25,124 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- import { parseString, parseValue } from './primitive.js';
29
- import { parseArray, parseInlineTable } from './struct.js';
30
- import { indexOfNewline, skipVoid, skipUntil, skipComment, getStringEnd } from './util.js';
28
+ import { parseKey } from './struct.js';
29
+ import { extractValue } from './extract.js';
30
+ import { skipVoid } from './util.js';
31
31
  import TomlError from './error.js';
32
- function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
33
- let value = str.slice(startPtr, endPtr);
34
- let commentIdx = value.indexOf('#');
35
- if (commentIdx > -1) {
36
- // The call to skipComment allows to "validate" the comment
37
- // (absence of control characters)
38
- skipComment(str, commentIdx);
39
- value = value.slice(0, commentIdx);
32
+ function peekTable(key, table, meta, type) {
33
+ let t = table;
34
+ let m = meta;
35
+ let k;
36
+ let hasOwn = false;
37
+ let state;
38
+ for (let i = 0; i < key.length; i++) {
39
+ if (i) {
40
+ t = hasOwn ? t[k] : (t[k] = {});
41
+ m = (state = m[k]).c;
42
+ if (type === 0 /* Type.DOTTED */ && state.t === 1 /* Type.EXPLICIT */) {
43
+ return null;
44
+ }
45
+ if (state.t === 2 /* Type.ARRAY */) {
46
+ let l = t.length - 1;
47
+ t = t[l];
48
+ m = m[l].c;
49
+ }
50
+ }
51
+ k = key[i];
52
+ if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 /* Type.DOTTED */ && m[k]?.d) {
53
+ return null;
54
+ }
55
+ if (!hasOwn) {
56
+ if (k === '__proto__') {
57
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
58
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
59
+ }
60
+ m[k] = {
61
+ t: i < key.length - 1 && type === 2 /* Type.ARRAY */
62
+ ? 0 /* Type.DOTTED */
63
+ : type,
64
+ d: false,
65
+ i: 0,
66
+ c: {},
67
+ };
68
+ }
40
69
  }
41
- let trimmed = value.trimEnd();
42
- if (!allowNewLines) {
43
- let newlineIdx = value.indexOf('\n', trimmed.length);
44
- if (newlineIdx > -1) {
45
- throw new TomlError('newlines are not allowed in inline tables', {
46
- toml: str,
47
- ptr: startPtr + newlineIdx
48
- });
70
+ state = m[k];
71
+ if (state.t !== type) {
72
+ // Bad key type!
73
+ return null;
74
+ }
75
+ if (type === 2 /* Type.ARRAY */) {
76
+ if (!state.d) {
77
+ state.d = true;
78
+ t[k] = [];
49
79
  }
80
+ t[k].push(t = {});
81
+ state.c[state.i++] = (state = { t: 1 /* Type.EXPLICIT */, d: false, i: 0, c: {} });
82
+ }
83
+ if (state.d) {
84
+ // Redefining a table!
85
+ return null;
86
+ }
87
+ state.d = true;
88
+ if (type === 1 /* Type.EXPLICIT */) {
89
+ t = hasOwn ? t[k] : (t[k] = {});
50
90
  }
51
- return [trimmed, commentIdx];
91
+ else if (type === 0 /* Type.DOTTED */ && hasOwn) {
92
+ return null;
93
+ }
94
+ return [k, t, state.c];
52
95
  }
53
- export function extractValue(str, ptr, end) {
54
- let c = str[ptr];
55
- if (c === '[' || c === '{') {
56
- let [value, endPtr] = c === '['
57
- ? parseArray(str, ptr)
58
- : parseInlineTable(str, ptr);
59
- let newPtr = skipUntil(str, endPtr, ',', end);
60
- if (end === '}') {
61
- let nextNewLine = indexOfNewline(str, endPtr, newPtr);
62
- if (nextNewLine > -1) {
63
- throw new TomlError('newlines are not allowed in inline tables', {
64
- toml: str,
65
- ptr: nextNewLine
96
+ export function parse(toml) {
97
+ let res = {};
98
+ let meta = {};
99
+ let tbl = res;
100
+ let m = meta;
101
+ for (let ptr = skipVoid(toml, 0); ptr < toml.length;) {
102
+ if (toml[ptr] === '[') {
103
+ let isTableArray = toml[++ptr] === '[';
104
+ let k = parseKey(toml, ptr += +isTableArray, ']');
105
+ if (isTableArray) {
106
+ if (toml[k[1] - 1] !== ']') {
107
+ throw new TomlError('expected end of table declaration', {
108
+ toml: toml,
109
+ ptr: k[1] - 1,
110
+ });
111
+ }
112
+ k[1]++;
113
+ }
114
+ let p = peekTable(k[0], res, meta, isTableArray ? 2 /* Type.ARRAY */ : 1 /* Type.EXPLICIT */);
115
+ if (!p) {
116
+ throw new TomlError('trying to redefine an already defined table or value', {
117
+ toml: toml,
118
+ ptr: ptr,
66
119
  });
67
120
  }
121
+ m = p[2];
122
+ tbl = p[1];
123
+ ptr = k[1];
68
124
  }
69
- return [value, newPtr];
70
- }
71
- let endPtr;
72
- if (c === '"' || c === "'") {
73
- endPtr = getStringEnd(str, ptr);
74
- return [parseString(str, ptr, endPtr), endPtr + +(!!end && str[endPtr] === ',')];
75
- }
76
- endPtr = skipUntil(str, ptr, ',', end);
77
- let slice = sliceAndTrimEndOf(str, ptr, endPtr - (+(str[endPtr - 1] === ',')), end === ']');
78
- if (!slice[0]) {
79
- throw new TomlError('incomplete key-value declaration: no value specified', {
80
- toml: str,
81
- ptr: ptr
82
- });
83
- }
84
- if (end && slice[1] > -1) {
85
- endPtr = skipVoid(str, ptr + slice[1]);
86
- endPtr += +(str[endPtr] === ',');
125
+ else {
126
+ let k = parseKey(toml, ptr);
127
+ let p = peekTable(k[0], tbl, m, 0 /* Type.DOTTED */);
128
+ if (!p) {
129
+ throw new TomlError('trying to redefine an already defined table or value', {
130
+ toml: toml,
131
+ ptr: ptr,
132
+ });
133
+ }
134
+ let v = extractValue(toml, k[1]);
135
+ p[1][p[0]] = v[0];
136
+ ptr = v[1];
137
+ }
138
+ ptr = skipVoid(toml, ptr, true);
139
+ if (toml[ptr] && toml[ptr] !== '\n' && toml[ptr] !== '\r') {
140
+ throw new TomlError('each key-value declaration must be followed by an end-of-line', {
141
+ toml: toml,
142
+ ptr: ptr
143
+ });
144
+ }
145
+ ptr = skipVoid(toml, ptr);
87
146
  }
88
- return [
89
- parseValue(slice[0], str, ptr),
90
- endPtr,
91
- ];
147
+ return res;
92
148
  }
package/dist/primitive.js CHANGED
@@ -135,10 +135,10 @@ export function parseValue(value, toml, ptr) {
135
135
  return Infinity;
136
136
  if (value === 'nan' || value === '+nan' || value === '-nan')
137
137
  return NaN;
138
- // Numbers
139
- let isInt;
140
138
  if (value === '-0')
141
139
  return 0; // Avoid FP representation of -0
140
+ // Numbers
141
+ let isInt;
142
142
  if ((isInt = INT_REGEX.test(value)) || FLOAT_REGEX.test(value)) {
143
143
  if (LEADING_ZERO.test(value)) {
144
144
  throw new TomlError('leading zeroes are not allowed', {
@@ -0,0 +1,28 @@
1
+ /*!
2
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright notice, this
9
+ * list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ * this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of the copyright holder nor the names of its contributors
14
+ * may be used to endorse or promote products derived from this software without
15
+ * specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+ export declare function stringify(obj: any): string;
@@ -0,0 +1,148 @@
1
+ /*!
2
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright notice, this
9
+ * list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ * this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of the copyright holder nor the names of its contributors
14
+ * may be used to endorse or promote products derived from this software without
15
+ * specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+ const BARE_KEY = /^[a-z0-9-_]+$/i;
29
+ function extendedTypeOf(obj) {
30
+ let type = typeof obj;
31
+ if (type === 'object') {
32
+ if (Array.isArray(obj))
33
+ return 'array';
34
+ if (obj instanceof Date)
35
+ return 'date';
36
+ }
37
+ return type;
38
+ }
39
+ function isArrayOfTables(obj) {
40
+ for (let i = 0; i < obj.length; i++) {
41
+ if (extendedTypeOf(obj[i]) !== 'object')
42
+ return false;
43
+ }
44
+ return true;
45
+ }
46
+ function formatString(s) {
47
+ return JSON.stringify(s).replace(/\x7f/g, '\\u007f');
48
+ }
49
+ function stringifyValue(val, type = extendedTypeOf(val)) {
50
+ if (type === 'number') {
51
+ if (isNaN(val))
52
+ return 'nan';
53
+ if (val === Infinity)
54
+ return 'inf';
55
+ if (val === -Infinity)
56
+ return '-inf';
57
+ return val.toString();
58
+ }
59
+ if (type === 'bigint' || type === 'boolean') {
60
+ return val.toString();
61
+ }
62
+ if (type === 'string') {
63
+ return formatString(val);
64
+ }
65
+ if (type === 'date') {
66
+ if (isNaN(val.getTime())) {
67
+ throw new TypeError('cannot serialize invalid date');
68
+ }
69
+ return val.toISOString();
70
+ }
71
+ if (type === 'object') {
72
+ return stringifyInlineTable(val);
73
+ }
74
+ if (type === 'array') {
75
+ return stringifyArray(val);
76
+ }
77
+ }
78
+ function stringifyInlineTable(obj) {
79
+ let res = '{ ';
80
+ let keys = Object.keys(obj);
81
+ for (let i = 0; i < keys.length; i++) {
82
+ let k = keys[i];
83
+ if (i)
84
+ res += ', ';
85
+ res += BARE_KEY.test(k) ? k : formatString(k);
86
+ res += ' = ';
87
+ res += stringifyValue(obj[k]);
88
+ }
89
+ return res + ' }';
90
+ }
91
+ function stringifyArray(array) {
92
+ let res = '[ ';
93
+ for (let i = 0; i < array.length; i++) {
94
+ if (i)
95
+ res += ', ';
96
+ if (array[i] === null || array[i] === void 0) {
97
+ throw new TypeError('arrays cannot contain null or undefined values');
98
+ }
99
+ res += stringifyValue(array[i]);
100
+ }
101
+ return res + ' ]';
102
+ }
103
+ function stringifyArrayTable(array, key) {
104
+ let res = '';
105
+ for (let i = 0; i < array.length; i++) {
106
+ res += `[[${key}]]\n`;
107
+ res += stringifyTable(array[i], key);
108
+ res += '\n\n';
109
+ }
110
+ return res;
111
+ }
112
+ function stringifyTable(obj, prefix = '') {
113
+ let preamble = '';
114
+ let tables = '';
115
+ let keys = Object.keys(obj);
116
+ for (let i = 0; i < keys.length; i++) {
117
+ let k = keys[i];
118
+ if (obj[k] !== null && obj[k] !== void 0) {
119
+ let type = extendedTypeOf(obj[k]);
120
+ if (type === 'symbol' || type === 'function') {
121
+ throw new TypeError(`cannot serialize values of type '${type}'`);
122
+ }
123
+ let key = BARE_KEY.test(k) ? k : formatString(k);
124
+ if (type === 'array' && isArrayOfTables(obj[k])) {
125
+ tables += stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key);
126
+ }
127
+ else if (type === 'object') {
128
+ let tblKey = prefix ? `${prefix}.${key}` : key;
129
+ tables += `[${tblKey}]\n`;
130
+ tables += stringifyTable(obj[k], tblKey);
131
+ tables += '\n\n';
132
+ }
133
+ else {
134
+ preamble += key;
135
+ preamble += ' = ';
136
+ preamble += stringifyValue(obj[k], type);
137
+ preamble += '\n';
138
+ }
139
+ }
140
+ }
141
+ return `${preamble}\n${tables}`.trim();
142
+ }
143
+ export function stringify(obj) {
144
+ if (extendedTypeOf(obj) !== 'object') {
145
+ throw new TypeError('stringify can only be called with an object');
146
+ }
147
+ return stringifyTable(obj);
148
+ }
package/dist/struct.js CHANGED
@@ -26,7 +26,7 @@
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
  import { parseString } from './primitive.js';
29
- import { extractValue } from './parse.js';
29
+ import { extractValue } from './extract.js';
30
30
  import { skipComment, indexOfNewline, getStringEnd, skipVoid } from './util.js';
31
31
  import TomlError from './error.js';
32
32
  let KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "smol-toml",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "keywords": [
5
5
  "toml",
6
- "parser"
6
+ "parser",
7
+ "serializer"
7
8
  ],
8
- "description": "A small, fast, and correct TOML parser",
9
+ "description": "A small, fast, and correct TOML parser/serializer",
9
10
  "repository": "git@github.com:squirrelchat/smol-toml.git",
10
11
  "author": "Cynthia <cyyynthia@borkenware.com>",
11
12
  "license": "BSD-3-Clause",