@limaformat/lima 0.5.1 → 0.7.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/CHANGELOG.md +99 -4
- package/README.md +6 -6
- package/dist/block-cursor.d.ts +4 -3
- package/dist/block-cursor.js +7 -3
- package/dist/block.js +123 -48
- package/dist/core.d.ts +4 -3
- package/dist/core.js +49 -30
- package/dist/document.d.ts +2 -0
- package/dist/document.js +29 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/key-grammar.d.ts +3 -0
- package/dist/key-grammar.js +58 -0
- package/dist/key-hints.d.ts +2 -0
- package/dist/key-hints.js +17 -0
- package/dist/reference-tokens2.d.ts +0 -5
- package/dist/reference-tokens2.js +65 -15
- package/dist/references2.js +8 -7
- package/dist/scalars.js +12 -7
- package/dist/scanner.js +28 -4
- package/dist/structure.d.ts +13 -0
- package/dist/structure.js +118 -0
- package/dist/unicode-key-table.d.ts +1 -0
- package/dist/unicode-key-table.js +245 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,109 @@ All notable changes to `@limaformat/lima` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
This package's version number tracks its own release history and is
|
|
7
|
-
independent of the Lima specification
|
|
8
|
-
1.
|
|
7
|
+
independent of the Lima specification versions — [Lima Core
|
|
8
|
+
1.2](https://github.com/limaformat/lima/blob/main/docs/lima-core-1.0-spec.md)
|
|
9
9
|
and [Lima References
|
|
10
|
-
1
|
|
11
|
-
|
|
10
|
+
2.1](https://github.com/limaformat/lima/blob/main/docs/lima-references-2.0-spec.md)
|
|
11
|
+
carry their own.
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.7.0] — 2026-10-30
|
|
16
|
+
|
|
17
|
+
Lima Core 1.2 and References 2.1: an unquoted key may be written in any
|
|
18
|
+
script. It starts with a letter or number of Unicode 18.0 or `_`, and after
|
|
19
|
+
its first character also holds marks, `*` and `·`, so keys can be written
|
|
20
|
+
in any language and gendered (`größe`, `題名`, `autor*in`, `autor·in`);
|
|
21
|
+
references reach every such key. Core 1.2 also states in one table what
|
|
22
|
+
each kind of line does where it stands (§7.1), without changing an outcome.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Unquoted keys and reference names take Unicode letters, marks and numbers
|
|
27
|
+
(Unicode 18.0, fixed by the specification) and `*` and `·` after their
|
|
28
|
+
first character. The categories come from a generated table, not the
|
|
29
|
+
host's Unicode support, and no dependency; ASCII keys are read as fast as
|
|
30
|
+
before.
|
|
31
|
+
- Hints: whitespace other than spaces before a key, a FULLWIDTH COLON
|
|
32
|
+
(U+FF1A) in place of `: `, and the key-grammar hint for a key such as
|
|
33
|
+
`x y: 2` at every level.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- A line whose key Core 1.1 rejected for its characters, and Core 1.2
|
|
38
|
+
admits, is an entry now; non-strict mode skipped it, strict mode threw.
|
|
39
|
+
- A document with a lone surrogate is rejected with `INVALID_DOCUMENT` and
|
|
40
|
+
the line it is on (Core §1), in both modes.
|
|
41
|
+
- Node.js 22 or later (`String.prototype.isWellFormed`).
|
|
42
|
+
|
|
43
|
+
## [0.6.0] — 2026-10-28
|
|
44
|
+
|
|
45
|
+
Lima Core 1.1: strict mode rejects a content line that belongs to no
|
|
46
|
+
structure, which Core 1.0 skipped in both modes. Core 1.1 also settles
|
|
47
|
+
cases Core 1.0 left open, where the three implementations differed; some
|
|
48
|
+
non-strict results change with them (below).
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Strict mode throws `INVALID_INDENTATION` for a line indented after a
|
|
53
|
+
top-level key with an inline value — the lines after a YAML `>`, `|-` or
|
|
54
|
+
`|+`, a key indented by a tab, freetext after a value — as it already did
|
|
55
|
+
one level down; the message names the YAML marker and what Lima writes
|
|
56
|
+
instead.
|
|
57
|
+
- Strict mode throws the new `UNRECOGNIZED_LINE` for an unindented line that
|
|
58
|
+
matches no key pattern: a key without its `: `, a dotted or otherwise
|
|
59
|
+
invalid key, a YAML `---` / `...` marker, a quoted key broken by a newline.
|
|
60
|
+
- A line of a block mapping whose key the key grammar rejects (`a.b: value`,
|
|
61
|
+
`bad key: value`), skipped in strict mode until now, throws `UNRECOGNIZED_LINE`
|
|
62
|
+
too; an indented line below a block mapping's base indentation, or after a
|
|
63
|
+
`|` scalar a column-0 comment ended, throws `INVALID_INDENTATION`.
|
|
64
|
+
- `LimaDiagnosticCode` gains `'UNRECOGNIZED_LINE'`.
|
|
65
|
+
- A bare key's sequence may start at the key's own column, as YAML writes
|
|
66
|
+
it (`tags:` / `- a` / `- b`), at any level (Core 1.1 §7.2). At the top
|
|
67
|
+
level this was read so by accident; one level down the sequence and the
|
|
68
|
+
keys after it were lost.
|
|
69
|
+
- An item whose value is a flow sequence (`- [a, b]`, `- []`) is a nested
|
|
70
|
+
sequence (Core 1.1 §7.2): `null` in non-strict mode, `INVALID_FLOW_SYNTAX`
|
|
71
|
+
in strict mode.
|
|
72
|
+
- Indentation is spaces only (Core 1.1 §7.1): a line whose spaces are followed
|
|
73
|
+
by other whitespace, such as U+00A0, before its content is unrecognised —
|
|
74
|
+
skipped in non-strict mode, an error in strict mode. Whitespace is defined
|
|
75
|
+
once (§1), as the set every check already used.
|
|
76
|
+
TypeScript counted such whitespace as indentation.
|
|
77
|
+
- An object item's later keys stand at its first key's column (§7.2): a line
|
|
78
|
+
of the item deeper than that is skipped in non-strict mode, and one less
|
|
79
|
+
indented ends the item; strict mode throws `INVALID_INDENTATION` for
|
|
80
|
+
either. Any deeper line was taken as a key of the item.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- A `|` scalar as an object item's first key measured its body from the dash
|
|
85
|
+
plus two columns, so after wider whitespace (`- a: |`) it took the item's
|
|
86
|
+
next key as text; it measures from the key's column (§6.1.5, §7.2).
|
|
87
|
+
- In strict mode, a line deeper than a nested `|` scalar's key, after a line
|
|
88
|
+
of other whitespace that ended the scalar, was dropped without an error; it
|
|
89
|
+
throws `INVALID_INDENTATION`.
|
|
90
|
+
- A top-level `key:` at the very end of the input, without a line
|
|
91
|
+
terminator, was no key at all and vanished; it is `null`, as at any nested
|
|
92
|
+
level and in the Rust and Go implementations, and as Core 1.1 §4 now says.
|
|
93
|
+
This changes a non-strict result too.
|
|
94
|
+
- A `|` with no content lines (`a: |` at the end of a block, or of the
|
|
95
|
+
input) was the string `"|"`; it is `""` (Core 1.1 §6.1.5).
|
|
96
|
+
- A block of mapping entries with a sequence item in it, or the reverse, was
|
|
97
|
+
an empty sequence in non-strict mode, all its entries lost; the first kind
|
|
98
|
+
wins and a line of the other kind is skipped (Core 1.1 §7.1).
|
|
99
|
+
- An item value that begins with `[` but is no flow sequence (`- [x] done`),
|
|
100
|
+
or with `{` and does not close (`- {a: 1`), throws `INVALID_FLOW_SYNTAX`
|
|
101
|
+
in strict mode, as an inline value does.
|
|
102
|
+
- `key: | # c` below the top level, in a block mapping or an object item,
|
|
103
|
+
was a block scalar; it is the string `"|"`, as at the top level — the
|
|
104
|
+
marker stands alone (§6.1.5).
|
|
105
|
+
|
|
106
|
+
The shared conformance corpus is at 336 Core cases: the 218 Core 1.0 cases
|
|
107
|
+
without the 8 that Core 1.1 supersedes, plus 126 Core 1.1 cases
|
|
108
|
+
(`corpus/core-1.1/`).
|
|
109
|
+
|
|
15
110
|
## [0.5.1] — 2026-10-28
|
|
16
111
|
|
|
17
112
|
Core errata `1.0.10`: aligned top-level values are trimmed, as Core 1.0
|
package/README.md
CHANGED
|
@@ -38,10 +38,10 @@ tags:
|
|
|
38
38
|
Three functions are exported:
|
|
39
39
|
|
|
40
40
|
- `parseCore(input, options?)` — the base format only.
|
|
41
|
-
- `parse(input, options?)` — the primary References 2.
|
|
41
|
+
- `parse(input, options?)` — the primary References <!-- facts:language.references_version -->2.1<!-- /facts --> parser; adds
|
|
42
42
|
`${key}` document references and `$(key)` external partials.
|
|
43
43
|
- `parseReferences(input, options?)` — deprecated compatibility alias for
|
|
44
|
-
`parse` with the same References 2.
|
|
44
|
+
`parse` with the same References <!-- facts:language.references_version -->2.1<!-- /facts --> semantics.
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
47
|
parse(frontmatter, { strict: true })
|
|
@@ -50,15 +50,15 @@ parse(frontmatter, { mode: 'core' }) // same reference-unaware path as parseCore
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
The **[guide](https://github.com/limaformat/lima/blob/main/docs/guide.md)**
|
|
53
|
-
covers Core and References 2.
|
|
53
|
+
covers Core <!-- facts:language.core_version -->1.2<!-- /facts --> and References <!-- facts:language.references_version -->2.1<!-- /facts --> syntax, resource limits, strict mode, and the
|
|
54
54
|
public API. The normative rules are defined in the
|
|
55
|
-
**[References 2.
|
|
55
|
+
**[References <!-- facts:language.references_version -->2.1<!-- /facts --> specification](https://github.com/limaformat/lima/blob/main/docs/lima-references-2.0-spec.md)**.
|
|
56
56
|
|
|
57
57
|
Why Lima exists, the case against YAML, and security rationale:
|
|
58
58
|
**[repository README](https://github.com/limaformat/lima#readme)**.
|
|
59
59
|
|
|
60
|
-
The [Lima Core 1.
|
|
61
|
-
and [Lima References 2.
|
|
60
|
+
The [Lima Core <!-- facts:language.core_version -->1.2<!-- /facts -->](https://github.com/limaformat/lima/blob/main/docs/lima-core-1.0-spec.md)
|
|
61
|
+
and [Lima References <!-- facts:language.references_version -->2.1<!-- /facts -->](https://github.com/limaformat/lima/blob/main/docs/lima-references-2.0-spec.md)
|
|
62
62
|
specifications are the source of truth for these public entry points. The
|
|
63
63
|
frozen References 1.0 implementation remains available internally to run its
|
|
64
64
|
independent conformance suite.
|
package/dist/block-cursor.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Allocation-free physical-line cursor over one top-level block extent.
|
|
3
|
-
* Positions are UTF-16 code-unit offsets.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Positions are UTF-16 code-unit offsets. Indentation is the run of spaces
|
|
4
|
+
* (Core 1.1 §7.1); other whitespace after it, such as U+00A0, starts the
|
|
5
|
+
* content of a line that has any, and only a blank or comment line looks
|
|
6
|
+
* past it (the complete whitespace predicate used by `trimStart()`).
|
|
6
7
|
*/
|
|
7
8
|
export declare class BlockCursor {
|
|
8
9
|
readonly source: string;
|
package/dist/block-cursor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { isTrimWhitespace } from './chars.js';
|
|
2
2
|
/**
|
|
3
3
|
* Allocation-free physical-line cursor over one top-level block extent.
|
|
4
|
-
* Positions are UTF-16 code-unit offsets.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Positions are UTF-16 code-unit offsets. Indentation is the run of spaces
|
|
5
|
+
* (Core 1.1 §7.1); other whitespace after it, such as U+00A0, starts the
|
|
6
|
+
* content of a line that has any, and only a blank or comment line looks
|
|
7
|
+
* past it (the complete whitespace predicate used by `trimStart()`).
|
|
7
8
|
*/
|
|
8
9
|
export class BlockCursor {
|
|
9
10
|
source;
|
|
@@ -50,6 +51,9 @@ export class BlockCursor {
|
|
|
50
51
|
} while (content < end);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
// other whitespace before content is no indentation: the content starts at it (Core 1.1 §7.1)
|
|
55
|
+
if (content > asciiEnd && content < end && this.source.charCodeAt(content) !== 35)
|
|
56
|
+
content = asciiEnd;
|
|
53
57
|
this.lineStart = start;
|
|
54
58
|
this.lineEnd = end;
|
|
55
59
|
this.contentStart = content;
|
package/dist/block.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { checkKeyLength, checkDuplicateKey, NESTING_DEPTH_LIMIT } from './normalize.js';
|
|
12
12
|
import { stripKeyQuotes, stripComment, stripCommentKeepEscapes, parseQuotedOrTyped, closingQuoteIndex, isValidKey, } from './scalars.js';
|
|
13
|
-
import {
|
|
13
|
+
import { KEY_GRAMMAR_HINT, specificKeyHint } from './key-hints.js';
|
|
14
|
+
import { parseFlowOrScalarValue, parseFlowSequence } from './flow.js';
|
|
14
15
|
import { buildBlockScalar } from './block-scalar.js';
|
|
15
16
|
import { LimaError } from './errors.js';
|
|
16
17
|
import { isTrimWhitespace } from './chars.js';
|
|
@@ -48,9 +49,10 @@ const inlineSource = (wantSource, ctx, keyLine, valueCol, rawUndecoded) => wantS
|
|
|
48
49
|
raw: physicalRaw(rawUndecoded), line: keyLine, col: valueCol,
|
|
49
50
|
tabAdjust: ctx.tabAdjust ? [ctx.tabAdjust[keyLine - 1] ?? 0] : undefined,
|
|
50
51
|
}) : undefined;
|
|
51
|
-
|
|
52
|
+
/** An inline value, or a `|` block scalar when `marker`: the value is `|` alone (§6.1.5; `| # c` is the string `|`). */
|
|
53
|
+
const inlineOrBlockScalar = (raw, marker, keyIndent, keyLine, valueCol, rawUndecoded, cursor, ctx, builder) => {
|
|
52
54
|
const wantSource = builder.positions === true;
|
|
53
|
-
if (
|
|
55
|
+
if (!marker) {
|
|
54
56
|
const value = parseFlowOrScalarValue(raw, ctx, keyLine, builder, inlineSource(wantSource, ctx, keyLine, valueCol, rawUndecoded));
|
|
55
57
|
cursor.next();
|
|
56
58
|
return value;
|
|
@@ -58,7 +60,9 @@ const inlineOrBlockScalar = (raw, keyIndent, keyLine, valueCol, rawUndecoded, cu
|
|
|
58
60
|
cursor.next(); // past the `key: |` line
|
|
59
61
|
const bodyLines = [];
|
|
60
62
|
while (cursor.valid) {
|
|
61
|
-
|
|
63
|
+
// the scalar's own extent (§6.1.5, `buildBlockScalar`): a line of spaces only is empty, any other ends it at the
|
|
64
|
+
// key's column or less — a line of other whitespace too, which is content here
|
|
65
|
+
if (cursor.lineEnd - cursor.lineStart > cursor.asciiIndent && cursor.asciiIndent <= keyIndent)
|
|
62
66
|
break;
|
|
63
67
|
bodyLines.push(cursor.source.slice(cursor.lineStart, cursor.lineEnd));
|
|
64
68
|
cursor.next();
|
|
@@ -83,6 +87,15 @@ export const findKeySep = (s) => {
|
|
|
83
87
|
* the original source. Strings are materialized only when grammar or scalar
|
|
84
88
|
* parsing needs their content.
|
|
85
89
|
*/
|
|
90
|
+
/**
|
|
91
|
+
* `source.slice(start, end).trimEnd()`: a key, or a line's content, keeps the whitespace it starts with, which makes it
|
|
92
|
+
* no key (Core 1.1 §7.1: other whitespace before content is no indentation).
|
|
93
|
+
*/
|
|
94
|
+
const trimEndSlice = (source, start, end) => {
|
|
95
|
+
while (end > start && isTrimWhitespace(source.charCodeAt(end - 1)))
|
|
96
|
+
end--;
|
|
97
|
+
return source.slice(start, end);
|
|
98
|
+
};
|
|
86
99
|
/** `source.slice(start, end).trim()` with no temporary untrimmed substring. */
|
|
87
100
|
const trimSlice = (source, start, end) => {
|
|
88
101
|
if (start < end) {
|
|
@@ -115,6 +128,30 @@ const skipEmptyAndCommentLines = (cursor) => {
|
|
|
115
128
|
while (cursor.valid && (cursor.empty || cursor.firstCode === 35))
|
|
116
129
|
cursor.next();
|
|
117
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* §7.2: a sequence item is a line of `-` alone or `-` followed by whitespace; `---` or `-x` is none (Core 1.1). The
|
|
133
|
+
* cursor is on a line with content.
|
|
134
|
+
*/
|
|
135
|
+
const isSequenceItem = (cursor) => {
|
|
136
|
+
if (cursor.firstCode !== 45)
|
|
137
|
+
return false;
|
|
138
|
+
const next = cursor.contentStart + 1;
|
|
139
|
+
return next === cursor.lineEnd || isTrimWhitespace(cursor.source.charCodeAt(next));
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Core 1.1 §7.2: the block of a bare key at `keyColumn` — deeper than the key (§7.1 rule 3), or a sequence that
|
|
143
|
+
* starts at the key's own column, which `compact` parsing ends at that column's first line that is no item. Null
|
|
144
|
+
* without either. The cursor is on the first content line after the key.
|
|
145
|
+
*/
|
|
146
|
+
const blockOfBareKey = (cursor, keyColumn, ctx, baseLine, builder) => {
|
|
147
|
+
if (!cursor.valid)
|
|
148
|
+
return null;
|
|
149
|
+
if (cursor.indent > keyColumn)
|
|
150
|
+
return parseCursorBlock(cursor, cursor.indent, ctx, baseLine, builder);
|
|
151
|
+
if (cursor.indent === keyColumn && isSequenceItem(cursor))
|
|
152
|
+
return parseCursorBlock(cursor, keyColumn, ctx, baseLine, builder, true);
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
118
155
|
const cursorAfterDash = (cursor) => {
|
|
119
156
|
const start = cursor.contentStart;
|
|
120
157
|
const end = cursor.lineEnd;
|
|
@@ -127,12 +164,19 @@ const cursorAfterDash = (cursor) => {
|
|
|
127
164
|
content++;
|
|
128
165
|
return cursor.source.slice(content, end);
|
|
129
166
|
};
|
|
167
|
+
/** Core 1.1 §10.1: a line in a block whose key the key grammar (§5.1) rejects, in strict mode. */
|
|
168
|
+
const unrecognizedKeyLine = (line, trimmed) => new LimaError({
|
|
169
|
+
code: 'UNRECOGNIZED_LINE', line,
|
|
170
|
+
message: `Lima: unrecognised line at line ${line}: "${trimmed}"${specificKeyHint(trimmed) ?? KEY_GRAMMAR_HINT}`,
|
|
171
|
+
});
|
|
130
172
|
/** Shared block grammar consuming one mutable physical-line cursor. */
|
|
131
|
-
const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
173
|
+
const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder, compact = false) => {
|
|
132
174
|
const wantSource = builder.positions === true;
|
|
133
175
|
let items = null;
|
|
134
176
|
let entries = null;
|
|
135
177
|
let pendingItem = null;
|
|
178
|
+
// the column of the pending object item's keys: its first key's, after `- ` (§7.2)
|
|
179
|
+
let pendingColumn = 0;
|
|
136
180
|
const startLine = cursor.lineIndex;
|
|
137
181
|
while (cursor.valid) {
|
|
138
182
|
const line = baseLine + cursor.lineIndex;
|
|
@@ -145,14 +189,30 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
145
189
|
break;
|
|
146
190
|
if (indent > baseIndent) {
|
|
147
191
|
const trimmed = cursorContent(cursor);
|
|
192
|
+
// §7.2: an object item's keys stand at its first key's column. A line less indented ends the item (§7.1
|
|
193
|
+
// rule 4); a deeper one, which no nested block took, is inconsistently indented (rule 5) and the item goes on
|
|
194
|
+
if (pendingItem !== null && indent < pendingColumn) {
|
|
195
|
+
items.push(builder.mapping(pendingItem, line));
|
|
196
|
+
pendingItem = null;
|
|
197
|
+
}
|
|
198
|
+
if (pendingItem !== null && indent > pendingColumn) {
|
|
199
|
+
if (ctx.strict)
|
|
200
|
+
throw new LimaError({ code: 'INVALID_INDENTATION', line,
|
|
201
|
+
message: `Lima: unexpected indentation at line ${line}: "${trimmed}"` });
|
|
202
|
+
cursor.next();
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
148
205
|
if (items !== null && pendingItem !== null) {
|
|
149
206
|
const bareKey = trimmed.charCodeAt(trimmed.length - 1) === 58;
|
|
150
207
|
const colonPos = bareKey && trimmed.indexOf(' ') === -1 ? -1 : findKeySep(trimmed);
|
|
151
208
|
const keyRaw = colonPos !== -1
|
|
152
|
-
?
|
|
153
|
-
: bareKey ?
|
|
209
|
+
? trimEndSlice(trimmed, 0, colonPos)
|
|
210
|
+
: bareKey ? trimEndSlice(trimmed, 0, trimmed.length - 1) : '';
|
|
154
211
|
if (keyRaw !== '' && !isValidKey(keyRaw)) {
|
|
155
|
-
// §5.1: not a usable key — skipped in
|
|
212
|
+
// §5.1: not a usable key — an unrecognised line, skipped in non-strict mode and an error in strict
|
|
213
|
+
// mode, as at the top level (Core 1.1 §10.1)
|
|
214
|
+
if (ctx.strict)
|
|
215
|
+
throw unrecognizedKeyLine(line, trimmed);
|
|
156
216
|
cursor.next();
|
|
157
217
|
}
|
|
158
218
|
else if (colonPos !== -1) {
|
|
@@ -163,19 +223,15 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
163
223
|
if (raw !== '|' && raw.includes('#'))
|
|
164
224
|
raw = stripComment(raw);
|
|
165
225
|
const valueCol = wantSource ? cursor.indent + colonPos + 2 + leadingWsLen(trimmed.slice(colonPos + 2)) : 0;
|
|
166
|
-
builder.setMapping(pendingItem, key, inlineOrBlockScalar(raw, indent, line, valueCol, rawUndecoded, cursor, ctx, builder));
|
|
226
|
+
builder.setMapping(pendingItem, key, inlineOrBlockScalar(raw, rawUndecoded === '|', indent, line, valueCol, rawUndecoded, cursor, ctx, builder));
|
|
167
227
|
}
|
|
168
228
|
else if (bareKey) {
|
|
169
229
|
const key = stripKeyQuotes(keyRaw, ctx.strict, line);
|
|
170
230
|
checkKeyLength(key, line);
|
|
171
231
|
cursor.next();
|
|
172
232
|
skipEmptyAndCommentLines(cursor);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
builder.setMapping(pendingItem, key, nested ?? builder.null(line));
|
|
176
|
-
}
|
|
177
|
-
else
|
|
178
|
-
builder.setMapping(pendingItem, key, builder.null(line));
|
|
233
|
+
const nested = blockOfBareKey(cursor, indent, ctx, baseLine, builder);
|
|
234
|
+
builder.setMapping(pendingItem, key, nested ?? builder.null(line));
|
|
179
235
|
}
|
|
180
236
|
else {
|
|
181
237
|
if (ctx.strict)
|
|
@@ -192,13 +248,16 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
192
248
|
}
|
|
193
249
|
continue;
|
|
194
250
|
}
|
|
195
|
-
|
|
251
|
+
// a sequence at its key's column ends at that column's first line that is no item (Core 1.1 §7.2)
|
|
252
|
+
if (compact && !isSequenceItem(cursor))
|
|
253
|
+
break;
|
|
254
|
+
if (isSequenceItem(cursor)) {
|
|
196
255
|
if (pendingItem !== null) {
|
|
197
256
|
items.push(builder.mapping(pendingItem, line));
|
|
198
257
|
pendingItem = null;
|
|
199
258
|
}
|
|
200
|
-
|
|
201
|
-
|
|
259
|
+
// a block is a mapping or a sequence, as its first line decides; in non-strict mode a line of the other kind
|
|
260
|
+
// is skipped, and what the block holds stays (Core 1.1 §7.1)
|
|
202
261
|
if (entries !== null) {
|
|
203
262
|
if (ctx.strict)
|
|
204
263
|
throw new LimaError({ code: 'INVALID_INDENTATION', line,
|
|
@@ -206,6 +265,8 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
206
265
|
cursor.next();
|
|
207
266
|
continue;
|
|
208
267
|
}
|
|
268
|
+
if (items === null)
|
|
269
|
+
items = [];
|
|
209
270
|
const afterDashRaw = cursorAfterDash(cursor);
|
|
210
271
|
let afterDash = afterDashRaw;
|
|
211
272
|
if (afterDash.includes('#'))
|
|
@@ -216,19 +277,33 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
216
277
|
raw: physicalRaw(afterDashRaw), line, col: afterDashCol, tabAdjust: afterDashTab,
|
|
217
278
|
}) : undefined;
|
|
218
279
|
const first = afterDash.charCodeAt(0);
|
|
219
|
-
|
|
280
|
+
// an item's value is an inline value (§15.3): a flow mapping, or what an unclosed one falls back to (§7.5); a
|
|
281
|
+
// flow sequence there is a sequence in a sequence (§7.2)
|
|
282
|
+
if (first === 123) {
|
|
283
|
+
items.push(parseFlowOrScalarValue(afterDash, ctx, line, builder, dashSource));
|
|
284
|
+
cursor.next();
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (first === 91) {
|
|
288
|
+
const sequence = parseFlowSequence(afterDash, ctx, line, builder, dashSource);
|
|
289
|
+
if (sequence === null)
|
|
290
|
+
items.push(parseFlowOrScalarValue(afterDash, ctx, line, builder, dashSource));
|
|
291
|
+
else if (ctx.strict)
|
|
292
|
+
throw new LimaError({ code: 'INVALID_FLOW_SYNTAX', line,
|
|
293
|
+
message: `Lima: nested sequence at line ${line}: "${cursorContent(cursor)}"` });
|
|
294
|
+
else
|
|
295
|
+
items.push(builder.null(line));
|
|
296
|
+
cursor.next();
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (first !== 34 && first !== 39 && first !== 45 &&
|
|
220
300
|
afterDash.indexOf(': ') === -1 && !afterDash.endsWith(':')) {
|
|
221
301
|
items.push(parseQuotedOrTyped(afterDash, ctx, line, builder, dashSource));
|
|
222
302
|
cursor.next();
|
|
223
303
|
continue;
|
|
224
304
|
}
|
|
225
|
-
const flowMap = parseFlowMapping(afterDash, ctx, line, builder, dashSource);
|
|
226
305
|
const colonPos = findKeySep(afterDash);
|
|
227
|
-
if (
|
|
228
|
-
items.push(flowMap);
|
|
229
|
-
cursor.next();
|
|
230
|
-
}
|
|
231
|
-
else if (afterDash === '-' || DASH_PREFIX_RE.test(afterDash)) {
|
|
306
|
+
if (afterDash === '-' || DASH_PREFIX_RE.test(afterDash)) {
|
|
232
307
|
if (ctx.strict)
|
|
233
308
|
throw new LimaError({ code: 'INVALID_INDENTATION', line,
|
|
234
309
|
message: `Lima: nested block sequence at line ${line}: "${cursorContent(cursor)}"` });
|
|
@@ -255,18 +330,20 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
255
330
|
? afterDash.slice(valueStart) : trimSlice(afterDash, valueStart, afterDash.length);
|
|
256
331
|
// The key sits after `- `, two columns past the dash.
|
|
257
332
|
const valueCol = wantSource ? afterDashCol + valueStart + leadingWsLen(afterDash.slice(valueStart)) : 0;
|
|
258
|
-
|
|
259
|
-
|
|
333
|
+
pendingColumn = cursor.indent + dashPrefixLen(cursor);
|
|
334
|
+
pendingItem = builder.createMappingWith(key, inlineOrBlockScalar(raw, trimSlice(afterDashRaw, valueStart, afterDashRaw.length) === '|', pendingColumn, line, valueCol, wantSource ? afterDashRaw.slice(valueStart) : raw, cursor, ctx, builder));
|
|
335
|
+
// the item's later keys at its key column; any other line is the block loop's (§7.2)
|
|
336
|
+
while (cursor.valid && cursor.indent === pendingColumn) {
|
|
260
337
|
const continuationLine = baseLine + cursor.lineIndex;
|
|
261
338
|
const ckeyIndent = cursor.asciiIndent;
|
|
262
339
|
const cfirst = cursor.source.charCodeAt(cursor.contentStart);
|
|
263
340
|
if (cfirst === 35)
|
|
264
341
|
break;
|
|
265
|
-
const contLine =
|
|
342
|
+
const contLine = trimEndSlice(cursor.source, cursor.contentStart, cursor.lineEnd);
|
|
266
343
|
const csep = findKeySep(contLine);
|
|
267
344
|
if (csep === -1)
|
|
268
345
|
break;
|
|
269
|
-
const ckeyRaw =
|
|
346
|
+
const ckeyRaw = trimEndSlice(contLine, 0, csep);
|
|
270
347
|
if (!isValidKey(ckeyRaw))
|
|
271
348
|
break;
|
|
272
349
|
const ckey = stripKeyQuotes(ckeyRaw, ctx.strict, continuationLine);
|
|
@@ -280,7 +357,7 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
280
357
|
// contLine starts exactly at contentStart (no leading trim), so
|
|
281
358
|
// the value's column is the indent plus its offset in contLine.
|
|
282
359
|
const cValueCol = wantSource ? cursor.indent + csep + 2 + leadingWsLen(contLine.slice(csep + 2)) : 0;
|
|
283
|
-
builder.setMapping(pendingItem, ckey, inlineOrBlockScalar(value, ckeyIndent, continuationLine, cValueCol, valueUndecoded, cursor, ctx, builder));
|
|
360
|
+
builder.setMapping(pendingItem, ckey, inlineOrBlockScalar(value, valueUndecoded === '|', ckeyIndent, continuationLine, cValueCol, valueUndecoded, cursor, ctx, builder));
|
|
284
361
|
}
|
|
285
362
|
}
|
|
286
363
|
else if (afterDash.endsWith(':') && isValidKey(trimSlice(afterDash, 0, afterDash.length - 1))) {
|
|
@@ -297,12 +374,9 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
297
374
|
keyColumn -= cursor.lineStart;
|
|
298
375
|
cursor.next();
|
|
299
376
|
skipEmptyAndCommentLines(cursor);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
else
|
|
305
|
-
pendingItem = builder.createMappingWith(key, builder.null(line));
|
|
377
|
+
const nested = blockOfBareKey(cursor, keyColumn, ctx, baseLine, builder);
|
|
378
|
+
pendingColumn = keyColumn;
|
|
379
|
+
pendingItem = builder.createMappingWith(key, nested ?? builder.null(line));
|
|
306
380
|
}
|
|
307
381
|
else {
|
|
308
382
|
// A quoted-or-typed scalar item — parseQuotedOrTyped enforces
|
|
@@ -323,11 +397,13 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
323
397
|
const bareKey = trimmed.charCodeAt(trimmed.length - 1) === 58;
|
|
324
398
|
const colonPos = bareKey && trimmed.indexOf(' ') === -1 ? -1 : findKeySep(trimmed);
|
|
325
399
|
const keyRaw = colonPos !== -1
|
|
326
|
-
?
|
|
327
|
-
: bareKey ?
|
|
400
|
+
? trimEndSlice(trimmed, 0, colonPos)
|
|
401
|
+
: bareKey ? trimEndSlice(trimmed, 0, trimmed.length - 1) : '';
|
|
328
402
|
if (keyRaw !== '' && !isValidKey(keyRaw)) {
|
|
329
|
-
// §5.1: not a usable key — unrecognised line, skipped in
|
|
330
|
-
//
|
|
403
|
+
// §5.1: not a usable key — an unrecognised line, skipped in non-strict mode and an error in strict
|
|
404
|
+
// mode, as at the top level (Core 1.1 §10.1)
|
|
405
|
+
if (ctx.strict)
|
|
406
|
+
throw unrecognizedKeyLine(line, trimmed);
|
|
331
407
|
cursor.next();
|
|
332
408
|
}
|
|
333
409
|
else if (colonPos !== -1) {
|
|
@@ -342,7 +418,7 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
342
418
|
if (raw !== '|' && raw.includes('#'))
|
|
343
419
|
raw = stripComment(raw);
|
|
344
420
|
const valueCol = wantSource ? cursor.indent + colonPos + 2 + leadingWsLen(trimmed.slice(colonPos + 2)) : 0;
|
|
345
|
-
builder.setMapping(entries, key, inlineOrBlockScalar(raw, baseIndent, line, valueCol, rawUndecoded, cursor, ctx, builder));
|
|
421
|
+
builder.setMapping(entries, key, inlineOrBlockScalar(raw, rawUndecoded === '|', baseIndent, line, valueCol, rawUndecoded, cursor, ctx, builder));
|
|
346
422
|
}
|
|
347
423
|
else if (bareKey) {
|
|
348
424
|
if (entries === null)
|
|
@@ -353,12 +429,8 @@ const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
|
|
|
353
429
|
checkDuplicateKey(builder.hasMappingKey(entries, key), key, line, ctx);
|
|
354
430
|
cursor.next();
|
|
355
431
|
skipEmptyAndCommentLines(cursor);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
builder.setMapping(entries, key, nested ?? builder.null(line));
|
|
359
|
-
}
|
|
360
|
-
else
|
|
361
|
-
builder.setMapping(entries, key, builder.null(line));
|
|
432
|
+
const nested = blockOfBareKey(cursor, baseIndent, ctx, baseLine, builder);
|
|
433
|
+
builder.setMapping(entries, key, nested ?? builder.null(line));
|
|
362
434
|
}
|
|
363
435
|
else {
|
|
364
436
|
if (ctx.strict)
|
|
@@ -381,7 +453,10 @@ export const parseBlockRange = (source, start, end, ctx, baseLine, builder, dept
|
|
|
381
453
|
if (!cursor.valid)
|
|
382
454
|
return null;
|
|
383
455
|
const baseIndent = cursor.asciiIndent;
|
|
384
|
-
|
|
456
|
+
// Core 1.1 §7.2: at the top level's own column 0 only a sequence is the key's block; any other first line is not
|
|
457
|
+
if (baseIndent === 0 && !isSequenceItem(cursor))
|
|
458
|
+
return null;
|
|
459
|
+
const value = parseCursorBlock(cursor, baseIndent, ctx, baseLine, builder, baseIndent === 0);
|
|
385
460
|
// Recursive block containers require strictly increasing integer
|
|
386
461
|
// indentation. At one indentation level a sequence-item mapping can add
|
|
387
462
|
// one container and Core flow syntax at most two more, so delta + 4 is a
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lima Core 1.
|
|
2
|
+
* Lima Core 1.1 parser — reference-unaware by construction (Appendix B:
|
|
3
3
|
* reference resolution is exclusively the concern of the optional
|
|
4
4
|
* References extension). `($key)`/`(%key)`-shaped text is never
|
|
5
5
|
* recognised or coerced here; it simply falls through as an ordinary
|
|
@@ -34,7 +34,7 @@ export type CoreOptions = {
|
|
|
34
34
|
onWarning?: (diagnostic: Diagnostic) => void;
|
|
35
35
|
};
|
|
36
36
|
/**
|
|
37
|
-
* Parses Lima Core 1.
|
|
37
|
+
* Parses Lima Core 1.1 syntax into the internal annotated value tree —
|
|
38
38
|
* every node carrying its source line, string leaves additionally carrying
|
|
39
39
|
* whether they came from quoted syntax. `($key)`/`(%key)` text is left
|
|
40
40
|
* exactly as written; nothing here ever inspects or resolves it. The
|
|
@@ -75,10 +75,11 @@ export declare const toNative: (v: LimaValue) => NativeValue;
|
|
|
75
75
|
*/
|
|
76
76
|
export declare const toNativeFromPositioned: (v: PositionedValue) => NativeValue;
|
|
77
77
|
/**
|
|
78
|
-
* Public Core 1.
|
|
78
|
+
* Public Core 1.1 entry point. Never resolves `($key)`/`(%key)` text — see
|
|
79
79
|
* the module doc comment. Equivalent in observable behavior to calling
|
|
80
80
|
* `parseReferences()` with no partials on a document that happens to
|
|
81
81
|
* contain no references, except that here reference-shaped text is
|
|
82
82
|
* guaranteed to always pass through unresolved, even in strict mode.
|
|
83
83
|
*/
|
|
84
|
+
export declare const parseCoreValidated: <T extends Record<string, unknown> = Meta>(frontMatter: string, options?: CoreOptions) => T;
|
|
84
85
|
export declare const parseCore: <T extends Record<string, unknown> = Meta>(frontMatter: string, options?: CoreOptions) => T;
|