@cavelang/parser 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.
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Token-stream line parser (spec §3.2, §16).
3
+ *
4
+ * Parses the token list of one line into claim/continuation/qualifier
5
+ * bodies. The payload/metadata boundary is decided lexically: metadata items
6
+ * are unambiguous at their first token (spec §4.3 reserved characters), so
7
+ * everything before the first metadata token is payload.
8
+ */
9
+ import { Claim, Confidence, Tag, Value, Verb } from '@cavelang/core';
10
+ const ok = (value, problems = []) => ({ ok: true, value, problems });
11
+ const fail = (message) => ({ ok: false, message });
12
+ const sigmaRe = /^\((\d+(?:\.\d+)?)σ\)$/u;
13
+ /** @returns `true` if `token` starts a metadata item (spec §4.3). */
14
+ export const isMetaStart = (token) => token.kind === 'word' && (token.text.startsWith('@') ||
15
+ token.text.startsWith('#') ||
16
+ token.text.startsWith('+/-') ||
17
+ token.text === '!' ||
18
+ sigmaRe.test(token.text));
19
+ /** @returns term from a single token (spec §16: atom, literal or code literal). */
20
+ const term = (token) => {
21
+ switch (token.kind) {
22
+ case 'text':
23
+ return Claim.text(token.text);
24
+ case 'code':
25
+ return Claim.code(token.text);
26
+ default:
27
+ return Claim.entity(token.text);
28
+ }
29
+ };
30
+ /** @returns value from payload value tokens (attribute value or metric). */
31
+ const valueOf = (tokens) => {
32
+ if (tokens.length === 0) {
33
+ return undefined;
34
+ }
35
+ const [head] = tokens;
36
+ if (tokens.length === 1 && head.kind === 'text') {
37
+ return Value.ofText(head.text);
38
+ }
39
+ if (tokens.length === 1 && head.kind === 'code') {
40
+ return Value.ofCode(head.text);
41
+ }
42
+ if (tokens.some(token => token.kind !== 'word')) {
43
+ return undefined;
44
+ }
45
+ return Value.parse(tokens.map(token => token.text).join(' '));
46
+ };
47
+ /**
48
+ * Parses metadata tokens (spec §6). Never fails — unrecognized tokens are
49
+ * reported as problems and skipped, honoring the robust-extraction goal
50
+ * (spec §1.6).
51
+ */
52
+ export const parseMeta = (tokens, comment) => {
53
+ const contexts = [];
54
+ const tags = [];
55
+ const problems = [];
56
+ let conf;
57
+ let importance = false;
58
+ let delta;
59
+ let sigmaLevel;
60
+ let i = 0;
61
+ const collectValueTokens = () => {
62
+ const collected = [];
63
+ while (i < tokens.length && !isMetaStart(tokens[i]) && tokens[i].kind === 'word') {
64
+ collected.push(tokens[i]);
65
+ i += 1;
66
+ }
67
+ return collected;
68
+ };
69
+ while (i < tokens.length) {
70
+ const token = tokens[i];
71
+ i += 1;
72
+ if (token.kind !== 'word') {
73
+ problems.push(`unexpected literal ${JSON.stringify(token.text)} in metadata position`);
74
+ continue;
75
+ }
76
+ const word = token.text;
77
+ if (word === '@') {
78
+ const next = tokens[i];
79
+ const text = next?.kind === 'word' ? next.text : undefined;
80
+ const parsed = text === undefined ? undefined : Confidence.parse(text);
81
+ if (parsed === undefined) {
82
+ // Consume a number-like token so `@ 90` / `@ 2026` yield one clear
83
+ // problem instead of a cascade (§6.3: confidence must end in %).
84
+ if (text !== undefined && /^\d/.test(text)) {
85
+ i += 1;
86
+ }
87
+ problems.push(`expected percentage ending in % after "@ "${text === undefined ? '' : `, got ${JSON.stringify(text)}`} (spec §6.3)`);
88
+ continue;
89
+ }
90
+ i += 1;
91
+ if (Number(text.slice(0, -1)) > 100) {
92
+ problems.push(`confidence ${text} above 100% clamped (spec §6.3)`);
93
+ }
94
+ if (conf !== undefined) {
95
+ problems.push('repeated confidence — only contexts and tags may repeat (spec §3.2); last one wins');
96
+ }
97
+ conf = parsed;
98
+ continue;
99
+ }
100
+ if (word.startsWith('@')) {
101
+ contexts.push(word.slice(1));
102
+ continue;
103
+ }
104
+ if (word === '#') {
105
+ problems.push('empty tag');
106
+ continue;
107
+ }
108
+ if (word.startsWith('#')) {
109
+ tags.push(Tag.parse(word.slice(1)));
110
+ continue;
111
+ }
112
+ if (word.startsWith('+/-')) {
113
+ const glued = word.slice('+/-'.length);
114
+ const collected = collectValueTokens().map(token_ => token_.text);
115
+ const raw = [...glued === '' ? [] : [glued], ...collected].join(' ');
116
+ if (raw === '') {
117
+ problems.push('expected value after +/- (spec §7.2)');
118
+ continue;
119
+ }
120
+ if (delta !== undefined) {
121
+ problems.push('repeated +/- uncertainty — only contexts and tags may repeat (spec §3.2); last one wins');
122
+ }
123
+ delta = Value.parse(raw);
124
+ continue;
125
+ }
126
+ const sigma = sigmaRe.exec(word);
127
+ if (sigma) {
128
+ if (sigmaLevel !== undefined) {
129
+ problems.push('repeated (Nσ) level — only contexts and tags may repeat (spec §3.2); last one wins');
130
+ }
131
+ sigmaLevel = Number(sigma[1]);
132
+ continue;
133
+ }
134
+ if (word === '!') {
135
+ if (importance) {
136
+ problems.push('repeated ! importance marker (spec §3.2)');
137
+ }
138
+ importance = true;
139
+ continue;
140
+ }
141
+ problems.push(`unexpected token ${JSON.stringify(word)} after payload`);
142
+ }
143
+ return {
144
+ meta: {
145
+ contexts,
146
+ tags,
147
+ importance,
148
+ ...conf !== undefined ? { conf } : {},
149
+ ...delta !== undefined ? { delta } : {},
150
+ ...sigmaLevel !== undefined ? { sigmaLevel } : {},
151
+ ...comment !== undefined ? { comment } : {}
152
+ },
153
+ problems
154
+ };
155
+ };
156
+ /** Splits tokens at the first metadata token. */
157
+ const splitAtMeta = (tokens) => {
158
+ const at = tokens.findIndex(isMetaStart);
159
+ return at === -1 ?
160
+ { payload: tokens, meta: [] } :
161
+ { payload: tokens.slice(0, at), meta: tokens.slice(at) };
162
+ };
163
+ /**
164
+ * Parses payload tokens (spec §16 `payload`):
165
+ *
166
+ * - `attr: value` — attribute claim; the colon is canonical (spec §3.4)
167
+ * - legacy colonless `HAS attr value` when the value is numeric/date-like
168
+ * - numeric/date value — metric claim (`latency IS 30ms`; also the shape a
169
+ * comparison condition canonicalizes to, e.g. `load EXCEEDS 1000 req/s`,
170
+ * which keeps `WHERE value > …` filters queryable, spec §12.2)
171
+ * - empty — bare existence, `EXISTS` only
172
+ * - otherwise — relational object (single term or multi-word phrase)
173
+ */
174
+ const parsePayload = (verb, tokens) => {
175
+ if (tokens.length === 0) {
176
+ return verb === 'EXISTS' ?
177
+ ok(Claim.none) :
178
+ fail(`missing object after ${verb} (the minimum line is "entity VERB object", spec §2.2)`);
179
+ }
180
+ const [head, ...rest] = tokens;
181
+ if (head.kind === 'word' && head.text.endsWith(':') && head.text.length > 1) {
182
+ const attribute = head.text.slice(0, -1);
183
+ const value = valueOf(rest);
184
+ if (value === undefined) {
185
+ return fail(`missing or mixed value after attribute ${JSON.stringify(attribute)} (spec §3.4)`);
186
+ }
187
+ return ok(Claim.attribute(attribute, value));
188
+ }
189
+ if (head.kind === 'word') {
190
+ // Glued colon: `expiry:3600s`. The payload `:` is reserved as the
191
+ // attribute/value separator (spec §4.3) — split it, keep a diagnostic.
192
+ const colonAt = head.text.indexOf(':');
193
+ if (colonAt > 0 && colonAt < head.text.length - 1) {
194
+ const attribute = head.text.slice(0, colonAt);
195
+ const value = valueOf([{ kind: 'word', text: head.text.slice(colonAt + 1) }, ...rest]);
196
+ if (value !== undefined) {
197
+ return ok(Claim.attribute(attribute, value), [`glued attribute colon; canonical is "${attribute}: ${value.raw}" — quote the object for a literal : (spec §3.4, §4.3)`]);
198
+ }
199
+ }
200
+ }
201
+ if (verb === 'HAS' && rest.length > 0 && head.kind === 'word') {
202
+ const value = valueOf(rest);
203
+ if (value !== undefined && (value.kind === 'number' || value.kind === 'date')) {
204
+ return ok(Claim.attribute(head.text, value), [`legacy colonless attribute form; canonical is "${head.text}: ${value.raw}" (spec §3.4)`]);
205
+ }
206
+ }
207
+ {
208
+ const value = valueOf(tokens);
209
+ if (value !== undefined && (value.kind === 'number' || value.kind === 'date')) {
210
+ return ok(Claim.metric(value));
211
+ }
212
+ }
213
+ if (tokens.length === 1) {
214
+ return ok(Claim.relation(term(head)));
215
+ }
216
+ if (tokens.every(token => token.kind === 'word')) {
217
+ return ok(Claim.relation(Claim.entity(tokens.map(token => token.text).join(' '))));
218
+ }
219
+ return fail('object phrase mixes words and literals — quote the whole object (spec §4.3)');
220
+ };
221
+ /** Parses `verb [NOT] payload metadata` — a continuation body (spec §8.3). */
222
+ export const parseBody = (tokens, comment) => {
223
+ const [head, ...rest] = tokens;
224
+ if (head === undefined || head.kind !== 'word' || !Verb.isVerbToken(head.text)) {
225
+ return fail(`expected an UPPERCASE verb, got ${JSON.stringify(head?.text ?? 'end of line')}`);
226
+ }
227
+ const verb = head.text;
228
+ const negated = rest[0]?.kind === 'word' && rest[0].text === 'NOT';
229
+ const afterNot = negated ? rest.slice(1) : rest;
230
+ const { payload, meta } = splitAtMeta(afterNot);
231
+ const payloadResult = parsePayload(verb, payload);
232
+ if (!payloadResult.ok) {
233
+ return payloadResult;
234
+ }
235
+ const parsedMeta = parseMeta(meta, comment);
236
+ return ok({ verb, negated, payload: payloadResult.value, meta: parsedMeta.meta }, [...payloadResult.problems, ...parsedMeta.problems]);
237
+ };
238
+ /** Parses `subject verb [NOT] payload metadata` — a full claim line (spec §16). */
239
+ export const parseClaim = (tokens, comment) => {
240
+ const [head, ...rest] = tokens;
241
+ if (head === undefined) {
242
+ return fail('empty claim line');
243
+ }
244
+ if (isMetaStart(head)) {
245
+ return fail(`expected a subject, got ${JSON.stringify(head.text)}`);
246
+ }
247
+ const body = parseBody(rest, comment);
248
+ if (!body.ok) {
249
+ return body;
250
+ }
251
+ return ok({ subject: term(head), ...body.value }, body.problems);
252
+ };
253
+ const isComparisonOp = (text) => ['>', '<', '>=', '<=', '=', '!='].includes(text);
254
+ /**
255
+ * Parses a qualifier payload (spec §8.2): a full claim, a comparison, or a
256
+ * bare entity — tried in that order.
257
+ */
258
+ export const parseQualifierPayload = (tokens, comment) => {
259
+ const negated = tokens[0]?.kind === 'word' && tokens[0].text === 'NOT';
260
+ const rest = negated ? tokens.slice(1) : tokens;
261
+ const [head, second] = rest;
262
+ if (head === undefined) {
263
+ return fail('empty qualifier payload');
264
+ }
265
+ if (second?.kind === 'word' &&
266
+ Verb.isVerbToken(second.text) &&
267
+ second.text !== 'NOT' &&
268
+ !isMetaStart(head)) {
269
+ const claim = parseClaim(rest, comment);
270
+ if (!claim.ok) {
271
+ return claim;
272
+ }
273
+ return ok({ kind: 'claim', negated, claim: claim.value }, claim.problems);
274
+ }
275
+ if (second?.kind === 'word' && isComparisonOp(second.text)) {
276
+ const { payload, meta } = splitAtMeta(rest.slice(2));
277
+ const value = valueOf(payload);
278
+ if (value === undefined) {
279
+ return fail(`missing value after comparison operator ${second.text}`);
280
+ }
281
+ const parsedMeta = parseMeta(meta, comment);
282
+ return ok({ kind: 'comparison', negated, left: term(head), op: second.text, value, meta: parsedMeta.meta }, parsedMeta.problems);
283
+ }
284
+ if (isMetaStart(head)) {
285
+ return fail(`expected qualifier payload, got ${JSON.stringify(head.text)}`);
286
+ }
287
+ const parsedMeta = parseMeta(rest.slice(1), comment);
288
+ if (rest.length > 1 && !isMetaStart(rest[1])) {
289
+ return fail('cannot parse qualifier payload as claim, comparison or entity');
290
+ }
291
+ return ok({ kind: 'entity', negated, term: term(head), meta: parsedMeta.meta }, parsedMeta.problems);
292
+ };
293
+ //# sourceMappingURL=line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"line.js","sourceRoot":"","sources":["../../src/line.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Line tokenizer (spec §4).
3
+ *
4
+ * Splits one line (indentation and comment already removed) into tokens:
5
+ * backticked code literals, double-quoted text literals, and whitespace
6
+ * separated words. Built on `@prelude/parser` combinators.
7
+ *
8
+ * Literals contain no escape sequences (the spec defines none) — a quoted
9
+ * token runs to the next matching delimiter. An unterminated delimiter
10
+ * degrades gracefully: the raw text is consumed as an ordinary word.
11
+ */
12
+ export type Token = {
13
+ readonly kind: 'word';
14
+ readonly text: string;
15
+ } | {
16
+ readonly kind: 'text';
17
+ readonly text: string;
18
+ } | {
19
+ readonly kind: 'code';
20
+ readonly text: string;
21
+ };
22
+ export type t = Token;
23
+ export declare const word: (text: string) => Token;
24
+ export declare const text: (value: string) => Token;
25
+ export declare const code: (value: string) => Token;
26
+ /** @returns tokens of a single line. Total — any input tokenizes. */
27
+ export declare const tokenize: (line: string) => Token[];
28
+ /**
29
+ * Splits a line at the first `;` that sits outside quotes and backticks
30
+ * (spec §6.4). @returns the head and the trimmed comment text, `undefined`
31
+ * when there is no comment or it is empty.
32
+ */
33
+ export declare const splitComment: (line: string) => {
34
+ head: string;
35
+ comment?: string;
36
+ };
37
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,KAAK,GACb;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpD,MAAM,MAAM,CAAC,GAAG,KAAK,CAAA;AAErB,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,KACV,CAAA;AAE1B,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,KAAG,KACJ,CAAA;AAEjC,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,KAAG,KACJ,CAAA;AAoBjC,qEAAqE;AACrE,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,KAAK,EAClB,CAAA;AAE3B;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAsB3E,CAAA"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Line tokenizer (spec §4).
3
+ *
4
+ * Splits one line (indentation and comment already removed) into tokens:
5
+ * backticked code literals, double-quoted text literals, and whitespace
6
+ * separated words. Built on `@prelude/parser` combinators.
7
+ *
8
+ * Literals contain no escape sequences (the spec defines none) — a quoted
9
+ * token runs to the next matching delimiter. An unterminated delimiter
10
+ * degrades gracefully: the raw text is consumed as an ordinary word.
11
+ */
12
+ import * as P from '@prelude/parser';
13
+ export const word = (text) => ({ kind: 'word', text });
14
+ export const text = (value) => ({ kind: 'text', text: value });
15
+ export const code = (value) => ({ kind: 'code', text: value });
16
+ const codeToken = P.map(P.seq(P.lit('`'), P.whileNotChars('`'), P.lit('`')), ([, body]) => code(body));
17
+ const textToken = P.map(P.seq(P.lit('"'), P.whileNotChars('"'), P.lit('"')), ([, body]) => text(body));
18
+ const wordToken = P.map(P.whileNotChars(' \t', 1), word);
19
+ const anyToken = P.first(codeToken, textToken, wordToken);
20
+ const lineTokens = P.map(P.seq(P.star(P.map(P.seq(P.ws0, anyToken), ([, token]) => token)), P.ws0), ([tokens]) => tokens);
21
+ /** @returns tokens of a single line. Total — any input tokenizes. */
22
+ export const tokenize = (line) => P.parse(lineTokens, line);
23
+ /**
24
+ * Splits a line at the first `;` that sits outside quotes and backticks
25
+ * (spec §6.4). @returns the head and the trimmed comment text, `undefined`
26
+ * when there is no comment or it is empty.
27
+ */
28
+ export const splitComment = (line) => {
29
+ let quote;
30
+ for (let i = 0; i < line.length; i++) {
31
+ const char = line[i];
32
+ if (quote !== undefined) {
33
+ if (char === quote) {
34
+ quote = undefined;
35
+ }
36
+ continue;
37
+ }
38
+ if (char === '"' || char === '`') {
39
+ quote = char;
40
+ continue;
41
+ }
42
+ if (char === ';') {
43
+ const comment = line.slice(i + 1).trim();
44
+ return comment === '' ?
45
+ { head: line.slice(0, i) } :
46
+ { head: line.slice(0, i), comment };
47
+ }
48
+ }
49
+ return { head: line };
50
+ };
51
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/token.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@cavelang/parser",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "CAVE text to AST parser built on @prelude/parser combinators.",
6
+ "license": "CC0-1.0",
7
+ "author": "Mirek Rusin (https://github.com/mirek)",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mirek/cave.git",
11
+ "directory": "packages/parser"
12
+ },
13
+ "engines": {
14
+ "node": ">=22.18"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/src/index.d.ts",
19
+ "default": "./dist/src/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "!dist/test",
25
+ "!dist/tsconfig.tsbuildinfo",
26
+ "src",
27
+ "!src/**/*.test.ts",
28
+ "README.md",
29
+ "License.md"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "dependencies": {
35
+ "@prelude/parser": "^7.1.0",
36
+ "@cavelang/core": "0.1.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -b",
40
+ "test": "node --disable-warning=ExperimentalWarning --test 'test/*.test.ts'",
41
+ "typecheck": "tsc -b"
42
+ }
43
+ }
package/src/ast.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * CAVE abstract syntax (spec §16).
3
+ *
4
+ * The parser turns text into `Line` values — pure syntax, before
5
+ * canonicalization. Inverse verbs are *not* resolved here (`packages/api
6
+ * PART-OF monorepo` parses with verb `PART-OF`); continuation lines keep
7
+ * their missing endpoint; qualifier payloads keep their surface shape.
8
+ * `@cavelang/canonical` applies the spec §13.4 pipeline on top of this AST.
9
+ */
10
+
11
+ import type { Claim, Value, Tag, Context, Verb } from '@cavelang/core'
12
+
13
+ /** Line metadata suffixes (spec §6), all optional. */
14
+ export type Meta = {
15
+ readonly contexts: readonly Context.t[]
16
+ readonly tags: readonly Tag.t[]
17
+ /** `@ N%` as decimal; `undefined` means the default 100%. */
18
+ readonly conf?: number
19
+ readonly importance: boolean
20
+ /** `+/- delta` (spec §7.2). */
21
+ readonly delta?: Value.t
22
+ /** `(Nσ)` override (spec §7.2). */
23
+ readonly sigmaLevel?: number
24
+ /** `; comment` (spec §6.4). */
25
+ readonly comment?: string
26
+ }
27
+
28
+ /** Empty metadata. */
29
+ export const emptyMeta: Meta =
30
+ { contexts: [], tags: [], importance: false }
31
+
32
+ /** Claim body without a subject — what a continuation line carries (spec §8.3). */
33
+ export type Body = {
34
+ readonly verb: string
35
+ readonly negated: boolean
36
+ readonly payload: Claim.Payload
37
+ readonly meta: Meta
38
+ }
39
+
40
+ /** Full claim: subject + body. */
41
+ export type Full = Body & {
42
+ readonly subject: Claim.Term
43
+ }
44
+
45
+ /** Comparison operators accepted in qualifier payloads (`WHEN load > ~1000 req/s`). */
46
+ export type ComparisonOp = '>' | '<' | '>=' | '<=' | '=' | '!='
47
+
48
+ export const comparisonOps: readonly ComparisonOp[] =
49
+ ['>', '<', '>=', '<=', '=', '!=']
50
+
51
+ /**
52
+ * Qualifier payload (spec §8.2). The grammar leaves `qualifier_payload`
53
+ * loose; three surface shapes cover the spec's examples:
54
+ *
55
+ * - full claim: `WHEN memory-leak EXISTS @ 60%`
56
+ * - bare entity: `WHEN cache-miss`, `WHEN NOT cache/enabled`
57
+ * - comparison: `WHEN load > ~1000 req/s`
58
+ *
59
+ * `negated` records a `NOT` immediately after the qualifier verb.
60
+ */
61
+ export type QualifierPayload =
62
+ | { readonly kind: 'claim', readonly negated: boolean, readonly claim: Full }
63
+ | { readonly kind: 'entity', readonly negated: boolean, readonly term: Claim.Term, readonly meta: Meta }
64
+ | { readonly kind: 'comparison', readonly negated: boolean, readonly left: Claim.Term, readonly op: ComparisonOp, readonly value: Value.t, readonly meta: Meta }
65
+
66
+ /**
67
+ * A parsed line. `depth` is the indentation width in characters; `parent` is
68
+ * the index (into the document's `lines`) of the nearest less-indented
69
+ * structural line above (spec §8). `raw` is the line exactly as written.
70
+ */
71
+ export type Line =
72
+ | { readonly kind: 'blank', readonly line: number, readonly raw: string }
73
+ | { readonly kind: 'comment', readonly line: number, readonly raw: string, readonly text: string }
74
+ | { readonly kind: 'claim', readonly line: number, readonly raw: string, readonly depth: number, readonly parent?: number, readonly claim: Full }
75
+ | { readonly kind: 'continuation', readonly line: number, readonly raw: string, readonly depth: number, readonly parent?: number, readonly body: Body }
76
+ | { readonly kind: 'qualifier', readonly line: number, readonly raw: string, readonly depth: number, readonly parent?: number, readonly qualifier: Verb.Qualifier, readonly payload: QualifierPayload }
77
+ | { readonly kind: 'invalid', readonly line: number, readonly raw: string, readonly message: string }
78
+
79
+ export type t = Line
80
+
81
+ /** Parser problem tied to a 1-based line number. */
82
+ export type Diagnostic = {
83
+ readonly line: number
84
+ readonly message: string
85
+ readonly raw: string
86
+ }
87
+
88
+ /** Parsed document: every input line classified, plus collected problems. */
89
+ export type Document = {
90
+ readonly lines: readonly Line[]
91
+ readonly diagnostics: readonly Diagnostic[]
92
+ }