@tabnas/transduce 0.1.1 → 0.2.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 +18 -0
- package/dist/guard.d.ts +1 -3
- package/dist/guard.js +8 -9
- package/dist/guard.js.map +1 -1
- package/dist/lines.d.ts +1 -2
- package/dist/lines.js +46 -89
- package/dist/lines.js.map +1 -1
- package/dist/matcher.d.ts +1 -3
- package/dist/matcher.js +12 -13
- package/dist/matcher.js.map +1 -1
- package/dist/parser-source.d.ts +1 -2
- package/dist/parser-source.js +11 -13
- package/dist/parser-source.js.map +1 -1
- package/dist/route.d.ts +1 -32
- package/dist/route.js +8 -32
- package/dist/route.js.map +1 -1
- package/dist/routers.d.ts +2 -0
- package/dist/routers.js +24 -0
- package/dist/routers.js.map +1 -0
- package/dist/rule-events.d.ts +1 -4
- package/dist/rule-events.js +40 -44
- package/dist/rule-events.js.map +1 -1
- package/dist/scan.d.ts +1 -10
- package/dist/scan.js +4 -17
- package/dist/scan.js.map +1 -1
- package/dist/source.d.ts +1 -4
- package/dist/source.js +20 -22
- package/dist/source.js.map +1 -1
- package/dist/table-from-json.d.ts +1 -5
- package/dist/table-from-json.js +25 -29
- package/dist/table-from-json.js.map +1 -1
- package/dist/transduce.d.ts +23 -19
- package/dist/transduce.js +66 -56
- package/dist/transduce.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -4
- package/src/guard.ts +10 -4
- package/src/lines.ts +45 -6
- package/src/matcher.ts +1 -6
- package/src/parser-source.ts +2 -4
- package/src/route.ts +18 -70
- package/src/routers.ts +64 -0
- package/src/rule-events.ts +17 -7
- package/src/scan.ts +1 -19
- package/src/source.ts +12 -6
- package/src/table-from-json.ts +20 -9
- package/src/transduce.ts +36 -19
- package/dist/datum.d.ts +0 -64
- package/dist/datum.js +0 -391
- package/dist/datum.js.map +0 -1
- package/dist/error.d.ts +0 -43
- package/dist/error.js +0 -169
- package/dist/error.js.map +0 -1
- package/dist/event.d.ts +0 -56
- package/dist/event.js +0 -101
- package/dist/event.js.map +0 -1
- package/dist/json.d.ts +0 -4
- package/dist/json.js +0 -92
- package/dist/json.js.map +0 -1
- package/dist/limits.d.ts +0 -36
- package/dist/limits.js +0 -133
- package/dist/limits.js.map +0 -1
- package/dist/selector.d.ts +0 -40
- package/dist/selector.js +0 -154
- package/dist/selector.js.map +0 -1
- package/dist/sink.d.ts +0 -29
- package/dist/sink.js +0 -184
- package/dist/sink.js.map +0 -1
- package/dist/table.d.ts +0 -81
- package/dist/table.js +0 -141
- package/dist/table.js.map +0 -1
- package/dist/value.d.ts +0 -16
- package/dist/value.js +0 -96
- package/dist/value.js.map +0 -1
- package/src/datum.ts +0 -407
- package/src/error.ts +0 -198
- package/src/event.ts +0 -134
- package/src/json.ts +0 -82
- package/src/limits.ts +0 -165
- package/src/selector.ts +0 -191
- package/src/sink.ts +0 -199
- package/src/table.ts +0 -213
- package/src/value.ts +0 -93
package/src/sink.ts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/* Copyright (c) 2026 tabnas, MIT License */
|
|
2
|
-
|
|
3
|
-
// The push boundary between stages.
|
|
4
|
-
//
|
|
5
|
-
// A pipeline is a chain of sinks. The source calls the first sink once per
|
|
6
|
-
// event, synchronously, inside the parse; each stage does its work and
|
|
7
|
-
// calls the next. Nothing is queued between stages, so a slow writer at the
|
|
8
|
-
// end slows the parser at the start: that is the backpressure, and it
|
|
9
|
-
// costs no buffer. A stage that must stop early (a `take`) answers
|
|
10
|
-
// `'stop'`, which the source turns into a cancelled parse. A stage that
|
|
11
|
-
// fails throws a `Fail`; the source stops the parse and the failure
|
|
12
|
-
// reaches the caller unchanged.
|
|
13
|
-
|
|
14
|
-
import { Fail } from './error'
|
|
15
|
-
import { JsonEvent } from './event'
|
|
16
|
-
import { Path, Segment } from './selector'
|
|
17
|
-
|
|
18
|
-
// What a stage wants next: `'continue'`, or `'stop'` (the stage has all it
|
|
19
|
-
// needs; the source stops the parse, releases what it holds and reports
|
|
20
|
-
// nothing further; not an error).
|
|
21
|
-
export type Flow = 'continue' | 'stop'
|
|
22
|
-
|
|
23
|
-
// A consumer of `JsonEvents/1`. `event` throws a `Fail` to fail the run.
|
|
24
|
-
export interface Sink {
|
|
25
|
-
event(ev: JsonEvent): Flow
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// A sink that records every event, for tests and small results.
|
|
29
|
-
export class EventRecorder implements Sink {
|
|
30
|
-
readonly events: JsonEvent[] = []
|
|
31
|
-
|
|
32
|
-
event(ev: JsonEvent): Flow {
|
|
33
|
-
this.events.push(ev)
|
|
34
|
-
return 'continue'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// A sink made of a function. A function that returns nothing continues.
|
|
39
|
-
export class FnSink implements Sink {
|
|
40
|
-
private fn: (ev: JsonEvent) => Flow | void
|
|
41
|
-
|
|
42
|
-
constructor(fn: (ev: JsonEvent) => Flow | void) {
|
|
43
|
-
this.fn = fn
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
event(ev: JsonEvent): Flow {
|
|
47
|
-
return this.fn(ev) || 'continue'
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// A sink that counts events and drops them: the cheapest consumer, for
|
|
52
|
-
// measuring a source on its own.
|
|
53
|
-
export class CountSink implements Sink {
|
|
54
|
-
events = 0
|
|
55
|
-
|
|
56
|
-
event(_ev: JsonEvent): Flow {
|
|
57
|
-
this.events++
|
|
58
|
-
return 'continue'
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Replay a recording into a sink, stopping where the sink stops.
|
|
63
|
-
export function replay(events: Iterable<JsonEvent>, sink: Sink): Flow {
|
|
64
|
-
for (const ev of events) {
|
|
65
|
-
if ('stop' === sink.event(ev)) return 'stop'
|
|
66
|
-
}
|
|
67
|
-
return 'continue'
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// A container `TreeContract` has open: an object with the keys it has had
|
|
71
|
-
// (the last of them the member whose value is due unless `keyDue`), or an
|
|
72
|
-
// array with the index of the element due next.
|
|
73
|
-
type Open =
|
|
74
|
-
| { array: false; keys: Set<string>; last: string | null; keyDue: boolean }
|
|
75
|
-
| { array: true; next: number }
|
|
76
|
-
|
|
77
|
-
// A tree's events, held to their contract in front of a sink that takes
|
|
78
|
-
// them as one (a render that writes a document from them): one root value,
|
|
79
|
-
// and in each object a key and then its value, each key once. A value
|
|
80
|
-
// walked from a parsed tree keeps it by construction. A parse streamed as
|
|
81
|
-
// it proceeds may not: it hands on a member its grammar reads twice
|
|
82
|
-
// (JSON's `{"a":1,"a":2}`, whose value keeps the last) where a tree has
|
|
83
|
-
// one, and the rule-event adapter refuses most shapes it cannot follow but
|
|
84
|
-
// not every one a grammar can produce. A repeated key in one object is
|
|
85
|
-
// refused with `DUPLICATE_MEMBER`, and events no tree has (a value where a
|
|
86
|
-
// key is due, a key outside an object, a close with nothing open, a second
|
|
87
|
-
// root) with `STREAMABILITY_UNKNOWN`, each at the path of the object
|
|
88
|
-
// concerned; the event is not passed on. Each open object keeps the keys
|
|
89
|
-
// it has had, dropped when it closes.
|
|
90
|
-
export class TreeContract<S extends Sink> implements Sink {
|
|
91
|
-
readonly inner: S
|
|
92
|
-
private open: Open[] = []
|
|
93
|
-
private rootDone = false
|
|
94
|
-
|
|
95
|
-
constructor(inner: S) {
|
|
96
|
-
this.inner = inner
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// The path of the value due next: the open containers, each by the member
|
|
100
|
-
// or element open in it, and in the innermost object its last key when
|
|
101
|
-
// that member's value is due.
|
|
102
|
-
private path(): Path {
|
|
103
|
-
const segments: Segment[] = []
|
|
104
|
-
const last = this.open.length - 1
|
|
105
|
-
this.open.forEach((open, i) => {
|
|
106
|
-
if (open.array) {
|
|
107
|
-
segments.push(open.next)
|
|
108
|
-
} else if ((i !== last || !open.keyDue) && null != open.last) {
|
|
109
|
-
// A container open inside this object is its last key's value,
|
|
110
|
-
// whatever `keyDue` says: the member was counted as taken when its
|
|
111
|
-
// value opened.
|
|
112
|
-
segments.push(open.last)
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
return new Path(segments)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private notATree(what: string): Fail {
|
|
119
|
-
return new Fail(
|
|
120
|
-
'STREAMABILITY_UNKNOWN',
|
|
121
|
-
`the stream holds ${what}, which a tree's events never do, so it is not a tree's`,
|
|
122
|
-
).atPath(this.path().toString())
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// A value is complete: the next in its array is due, or the root is.
|
|
126
|
-
private closed(): void {
|
|
127
|
-
const top = this.open[this.open.length - 1]
|
|
128
|
-
if (undefined === top) this.rootDone = true
|
|
129
|
-
else if (top.array) top.next++
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
event(ev: JsonEvent): Flow {
|
|
133
|
-
const top = this.open[this.open.length - 1]
|
|
134
|
-
switch (ev.type) {
|
|
135
|
-
case 'key': {
|
|
136
|
-
if (undefined !== top && !top.array && top.keyDue) {
|
|
137
|
-
if (top.keys.has(ev.key)) {
|
|
138
|
-
const path = this.path()
|
|
139
|
-
path.push(ev.key)
|
|
140
|
-
throw new Fail(
|
|
141
|
-
'DUPLICATE_MEMBER',
|
|
142
|
-
`member ${JSON.stringify(ev.key)} appears twice in one object, and a tree's ` +
|
|
143
|
-
`events hold each key once`,
|
|
144
|
-
).atPath(path.toString())
|
|
145
|
-
}
|
|
146
|
-
top.keys.add(ev.key)
|
|
147
|
-
top.last = ev.key
|
|
148
|
-
top.keyDue = false
|
|
149
|
-
} else if (undefined !== top && !top.array) {
|
|
150
|
-
throw this.notATree('a key where a value is due')
|
|
151
|
-
} else {
|
|
152
|
-
throw this.notATree('a key outside an object')
|
|
153
|
-
}
|
|
154
|
-
break
|
|
155
|
-
}
|
|
156
|
-
case 'object_end':
|
|
157
|
-
if (undefined !== top && !top.array && top.keyDue) {
|
|
158
|
-
this.open.pop()
|
|
159
|
-
this.closed()
|
|
160
|
-
} else if (undefined !== top && !top.array) {
|
|
161
|
-
throw this.notATree("an object's end where a value is due")
|
|
162
|
-
} else {
|
|
163
|
-
throw this.notATree("an object's end where none is due")
|
|
164
|
-
}
|
|
165
|
-
break
|
|
166
|
-
case 'array_end':
|
|
167
|
-
if (undefined !== top && top.array) {
|
|
168
|
-
this.open.pop()
|
|
169
|
-
this.closed()
|
|
170
|
-
} else {
|
|
171
|
-
throw this.notATree("an array's end where none is due")
|
|
172
|
-
}
|
|
173
|
-
break
|
|
174
|
-
case 'end':
|
|
175
|
-
if (0 < this.open.length) {
|
|
176
|
-
throw this.notATree('its end inside an open container')
|
|
177
|
-
}
|
|
178
|
-
break
|
|
179
|
-
default: {
|
|
180
|
-
// A value: in an object, only once its key is in; at the root,
|
|
181
|
-
// only once.
|
|
182
|
-
if (undefined === top) {
|
|
183
|
-
if (this.rootDone) throw this.notATree('a second root value')
|
|
184
|
-
} else if (!top.array) {
|
|
185
|
-
if (top.keyDue) throw this.notATree('a value where a key is due')
|
|
186
|
-
top.keyDue = true
|
|
187
|
-
}
|
|
188
|
-
if ('object_start' === ev.type) {
|
|
189
|
-
this.open.push({ array: false, keys: new Set(), last: null, keyDue: true })
|
|
190
|
-
} else if ('array_start' === ev.type) {
|
|
191
|
-
this.open.push({ array: true, next: 0 })
|
|
192
|
-
} else {
|
|
193
|
-
this.closed()
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return this.inner.event(ev)
|
|
198
|
-
}
|
|
199
|
-
}
|
package/src/table.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/* Copyright (c) 2026 tabnas, MIT License */
|
|
2
|
-
|
|
3
|
-
// `TableRows/1`: one schema, ordered finite rows, completion.
|
|
4
|
-
//
|
|
5
|
-
// The protocol is flat and budgeted: a row is a finite vector of cells,
|
|
6
|
-
// already projected into schema order by the transducer, and a renderer
|
|
7
|
-
// never sees a source path. Public columns carry a label; the source
|
|
8
|
-
// binding (`BoundColumn`) stays on the transducer's side of the boundary.
|
|
9
|
-
|
|
10
|
-
import { Datum, toText } from './datum'
|
|
11
|
-
import { Fail } from './error'
|
|
12
|
-
import { jsonNumber, jsonString } from './json'
|
|
13
|
-
import { NODE_BYTES, utf8Bytes } from './limits'
|
|
14
|
-
import { Segment, Selector } from './selector'
|
|
15
|
-
import { Flow } from './sink'
|
|
16
|
-
|
|
17
|
-
// One projected value. `missing`: the source had no value at the column's
|
|
18
|
-
// path; not null, not the empty string, not zero: a policy maps or rejects
|
|
19
|
-
// it later.
|
|
20
|
-
export type Cell =
|
|
21
|
-
| { readonly type: 'null' }
|
|
22
|
-
| { readonly type: 'bool'; readonly value: boolean }
|
|
23
|
-
| { readonly type: 'number'; readonly value: number; readonly lexeme: string | null }
|
|
24
|
-
| { readonly type: 'string'; readonly value: string }
|
|
25
|
-
| { readonly type: 'missing' }
|
|
26
|
-
|
|
27
|
-
const NULL: Cell = Object.freeze({ type: 'null' })
|
|
28
|
-
const MISSING: Cell = Object.freeze({ type: 'missing' })
|
|
29
|
-
|
|
30
|
-
export const Cell = Object.freeze({
|
|
31
|
-
null: NULL,
|
|
32
|
-
missing: MISSING,
|
|
33
|
-
bool(value: boolean): Cell {
|
|
34
|
-
return { type: 'bool', value }
|
|
35
|
-
},
|
|
36
|
-
number(value: number, lexeme?: string | null): Cell {
|
|
37
|
-
return { type: 'number', value, lexeme: lexeme ?? null }
|
|
38
|
-
},
|
|
39
|
-
string(value: string): Cell {
|
|
40
|
-
return { type: 'string', value }
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
// From a retained value. A container is not a cell; it is serialized as
|
|
44
|
-
// compact JSON text, the lossy but unambiguous choice the standard
|
|
45
|
-
// binding makes and documents.
|
|
46
|
-
fromDatum(d: Datum): Cell {
|
|
47
|
-
switch (d.type) {
|
|
48
|
-
case 'null':
|
|
49
|
-
return NULL
|
|
50
|
-
case 'bool':
|
|
51
|
-
return { type: 'bool', value: d.value }
|
|
52
|
-
case 'number':
|
|
53
|
-
return { type: 'number', value: d.value, lexeme: d.lexeme }
|
|
54
|
-
case 'string':
|
|
55
|
-
return { type: 'string', value: d.value }
|
|
56
|
-
default:
|
|
57
|
-
return { type: 'string', value: toText(d) }
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
// The bytes this cell retains, on the same basis as a datum's size.
|
|
62
|
-
byteSize(c: Cell): number {
|
|
63
|
-
switch (c.type) {
|
|
64
|
-
case 'number':
|
|
65
|
-
return NODE_BYTES + (null != c.lexeme ? utf8Bytes(c.lexeme) : 8)
|
|
66
|
-
case 'string':
|
|
67
|
-
return NODE_BYTES + utf8Bytes(c.value)
|
|
68
|
-
default:
|
|
69
|
-
return NODE_BYTES
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
// The cell as JSON text; `missing` prints as `missing`.
|
|
74
|
-
toText(c: Cell): string {
|
|
75
|
-
switch (c.type) {
|
|
76
|
-
case 'null':
|
|
77
|
-
return 'null'
|
|
78
|
-
case 'bool':
|
|
79
|
-
return String(c.value)
|
|
80
|
-
case 'number':
|
|
81
|
-
return jsonNumber(c.value, c.lexeme)
|
|
82
|
-
case 'string':
|
|
83
|
-
return jsonString(c.value)
|
|
84
|
-
case 'missing':
|
|
85
|
-
return 'missing'
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// What a renderer knows about a column.
|
|
91
|
-
export type PublicColumn = { label: string }
|
|
92
|
-
|
|
93
|
-
// One event of `TableRows/1`: the `schema` exactly once and first, a `row`
|
|
94
|
-
// per row (each exactly as wide as the schema), the `end` exactly once and
|
|
95
|
-
// last, and only after the source validated to its end.
|
|
96
|
-
export type TableEvent =
|
|
97
|
-
| { readonly type: 'schema'; readonly columns: readonly PublicColumn[] }
|
|
98
|
-
| { readonly type: 'row'; readonly cells: readonly Cell[] }
|
|
99
|
-
| { readonly type: 'end' }
|
|
100
|
-
|
|
101
|
-
// A consumer of `TableRows/1`. Throws a `Fail` to fail the run.
|
|
102
|
-
export interface TableSink {
|
|
103
|
-
tableEvent(ev: TableEvent): Flow
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// An owned recording of a table, for tests and small results.
|
|
107
|
-
export class Table implements TableSink {
|
|
108
|
-
columns: PublicColumn[] = []
|
|
109
|
-
rows: Cell[][] = []
|
|
110
|
-
ended = false
|
|
111
|
-
|
|
112
|
-
tableEvent(ev: TableEvent): Flow {
|
|
113
|
-
switch (ev.type) {
|
|
114
|
-
case 'schema':
|
|
115
|
-
this.columns = ev.columns.slice()
|
|
116
|
-
break
|
|
117
|
-
case 'row':
|
|
118
|
-
this.rows.push(ev.cells.slice())
|
|
119
|
-
break
|
|
120
|
-
case 'end':
|
|
121
|
-
this.ended = true
|
|
122
|
-
break
|
|
123
|
-
}
|
|
124
|
-
return 'continue'
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// What to do when a row has no value at a column's path: deliver a
|
|
129
|
-
// `missing` cell (the default; the renderer's policy decides), deliver
|
|
130
|
-
// `null`, or fail the run with `MISSING_VALUE`.
|
|
131
|
-
export type MissingPolicy = 'missing' | 'null' | 'error'
|
|
132
|
-
|
|
133
|
-
// A column as the transducer binds it: the public label, and the source
|
|
134
|
-
// path projected from each row. Never crosses into a renderer.
|
|
135
|
-
export type BoundColumn = {
|
|
136
|
-
label: string
|
|
137
|
-
source: Segment[]
|
|
138
|
-
missing: MissingPolicy
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function boundColumn(
|
|
142
|
-
label: string,
|
|
143
|
-
source: Segment[],
|
|
144
|
-
missing: MissingPolicy = 'missing',
|
|
145
|
-
): BoundColumn {
|
|
146
|
-
return { label, source, missing }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Maps one metadata descriptor to a bound column; throws a `Fail` for a
|
|
150
|
-
// descriptor it cannot read.
|
|
151
|
-
export type ColumnMapper = (meta: Datum) => BoundColumn
|
|
152
|
-
|
|
153
|
-
// Where a table's columns come from: declared by the caller (`static`),
|
|
154
|
-
// selected from the source and mapped one descriptor at a time
|
|
155
|
-
// (`metadata`; the metadata must complete before the first row begins),
|
|
156
|
-
// or the first row's member names in its order (`infer`; data-dependent: a
|
|
157
|
-
// later row's extra members are dropped, its absent ones are `missing`).
|
|
158
|
-
export type Schema =
|
|
159
|
-
| { type: 'static'; columns: BoundColumn[] }
|
|
160
|
-
| { type: 'metadata'; columns: Selector; column: ColumnMapper }
|
|
161
|
-
| { type: 'infer' }
|
|
162
|
-
|
|
163
|
-
export const Schema = Object.freeze({
|
|
164
|
-
static(columns: BoundColumn[]): Schema {
|
|
165
|
-
return { type: 'static', columns }
|
|
166
|
-
},
|
|
167
|
-
fromMetadata(columns: Selector, column: ColumnMapper = columnFromMeta): Schema {
|
|
168
|
-
return { type: 'metadata', columns, column }
|
|
169
|
-
},
|
|
170
|
-
infer(): Schema {
|
|
171
|
-
return { type: 'infer' }
|
|
172
|
-
},
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
// A table transducer's source binding: the schema, and the rows (each
|
|
176
|
-
// location `rows` names is one row).
|
|
177
|
-
export type TableBinding = {
|
|
178
|
-
schema: Schema
|
|
179
|
-
rows: Selector
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// The standard mapping from a metadata descriptor to a column: the spec's
|
|
183
|
-
// `column-from-meta`, reading `title` and a `path` of segments.
|
|
184
|
-
export function columnFromMeta(meta: Datum): BoundColumn {
|
|
185
|
-
if ('object' !== meta.type) {
|
|
186
|
-
throw Fail.input('a column descriptor is not an object')
|
|
187
|
-
}
|
|
188
|
-
const title = meta.members.get('title')
|
|
189
|
-
if (undefined === title || 'string' !== title.type) {
|
|
190
|
-
throw Fail.input('a column descriptor has no string "title"')
|
|
191
|
-
}
|
|
192
|
-
const label = title.value
|
|
193
|
-
const path = meta.members.get('path')
|
|
194
|
-
if (undefined === path || 'array' !== path.type) {
|
|
195
|
-
throw Fail.input(`column ${JSON.stringify(label)} has no "path" array`)
|
|
196
|
-
}
|
|
197
|
-
const source: Segment[] = path.items.map((seg) => {
|
|
198
|
-
if ('string' === seg.type) return seg.value
|
|
199
|
-
if (
|
|
200
|
-
'number' === seg.type &&
|
|
201
|
-
seg.value >= 0 &&
|
|
202
|
-
Number.isInteger(seg.value) &&
|
|
203
|
-
seg.value <= 0xffffffff
|
|
204
|
-
) {
|
|
205
|
-
return seg.value
|
|
206
|
-
}
|
|
207
|
-
throw Fail.input(
|
|
208
|
-
`column ${JSON.stringify(label)} has a path segment that is neither a string nor a ` +
|
|
209
|
-
`non-negative integer: ${toText(seg)}`,
|
|
210
|
-
)
|
|
211
|
-
})
|
|
212
|
-
return boundColumn(label, source)
|
|
213
|
-
}
|
package/src/value.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/* Copyright (c) 2026 tabnas, MIT License */
|
|
2
|
-
|
|
3
|
-
// Reading the engine's values: what a tabnas parse returns in TypeScript.
|
|
4
|
-
//
|
|
5
|
-
// The engine builds plain JavaScript values: `null` and `undefined`,
|
|
6
|
-
// booleans, numbers, strings, arrays and objects. A few grammars hand over
|
|
7
|
-
// host types, which read as the scalars they stand for: a boxed `String`
|
|
8
|
-
// (the engine's text annotation), a `BigInt`, and a TOML `Date`, which
|
|
9
|
-
// reads as the source text it carries (`__toml__.src`), as the Rust
|
|
10
|
-
// runtime's date does.
|
|
11
|
-
//
|
|
12
|
-
// An object's member order is its insertion order. A plain object
|
|
13
|
-
// enumerates integer-like keys first whatever order they were written in,
|
|
14
|
-
// so the engine records the order on the node under `map.ordered` (read
|
|
15
|
-
// with `KEY_ORDER`); the sources switch that on, and `engineKeys` reads
|
|
16
|
-
// it, falling back to `Object.keys`.
|
|
17
|
-
|
|
18
|
-
// The engine's key-order side channel (`keyOrder` in @tabnas/parser).
|
|
19
|
-
const KEY_ORDER = Symbol.for('tabnas.keyOrder')
|
|
20
|
-
|
|
21
|
-
export type EngineScalar =
|
|
22
|
-
| { type: 'null' }
|
|
23
|
-
| { type: 'bool'; value: boolean }
|
|
24
|
-
| { type: 'number'; value: number }
|
|
25
|
-
| { type: 'string'; value: string }
|
|
26
|
-
|
|
27
|
-
const NULL: EngineScalar = { type: 'null' }
|
|
28
|
-
|
|
29
|
-
// The value as a scalar, or `undefined` when it is a container.
|
|
30
|
-
export function engineScalar(v: unknown): EngineScalar | undefined {
|
|
31
|
-
switch (typeof v) {
|
|
32
|
-
case 'undefined':
|
|
33
|
-
return NULL
|
|
34
|
-
case 'boolean':
|
|
35
|
-
return { type: 'bool', value: v }
|
|
36
|
-
case 'number':
|
|
37
|
-
return { type: 'number', value: v }
|
|
38
|
-
case 'bigint':
|
|
39
|
-
return { type: 'number', value: Number(v) }
|
|
40
|
-
case 'string':
|
|
41
|
-
return { type: 'string', value: v }
|
|
42
|
-
case 'object':
|
|
43
|
-
if (null === v) return NULL
|
|
44
|
-
if (Array.isArray(v)) return undefined
|
|
45
|
-
if (v instanceof String) return { type: 'string', value: v.valueOf() }
|
|
46
|
-
if (v instanceof Number) return { type: 'number', value: v.valueOf() }
|
|
47
|
-
if (v instanceof Boolean) return { type: 'bool', value: v.valueOf() }
|
|
48
|
-
if (v instanceof Date) {
|
|
49
|
-
const src = (v as any).__toml__?.src
|
|
50
|
-
return { type: 'string', value: 'string' === typeof src ? src : v.toJSON() ?? 'null' }
|
|
51
|
-
}
|
|
52
|
-
return undefined
|
|
53
|
-
default:
|
|
54
|
-
// A function or a symbol has no JSON form.
|
|
55
|
-
return NULL
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Whether the value is a container: an array, or an object that is no
|
|
60
|
-
// scalar's host type.
|
|
61
|
-
export function isEngineContainer(v: unknown): boolean {
|
|
62
|
-
return null !== v && 'object' === typeof v && undefined === engineScalar(v)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Whether the value is a map (a container that is not an array).
|
|
66
|
-
export function isEngineMap(v: unknown): v is Record<string, unknown> {
|
|
67
|
-
return isEngineContainer(v) && !Array.isArray(v)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// A map's member names, in insertion order: the engine's recorded order
|
|
71
|
-
// where it kept one, each name once and only while the map still holds it,
|
|
72
|
-
// then any member the record missed.
|
|
73
|
-
export function engineKeys(v: unknown): string[] {
|
|
74
|
-
if (null === v || 'object' !== typeof v) return []
|
|
75
|
-
const own = Object.keys(v as object)
|
|
76
|
-
const order: unknown = (v as any)[KEY_ORDER]
|
|
77
|
-
if (!Array.isArray(order) || 0 === order.length) return own
|
|
78
|
-
const has = Object.prototype.hasOwnProperty
|
|
79
|
-
const seen = new Set<string>()
|
|
80
|
-
const out: string[] = []
|
|
81
|
-
for (const k of order) {
|
|
82
|
-
const key = String(k)
|
|
83
|
-
if (seen.has(key) || !has.call(v, key)) continue
|
|
84
|
-
seen.add(key)
|
|
85
|
-
out.push(key)
|
|
86
|
-
}
|
|
87
|
-
if (out.length !== own.length) {
|
|
88
|
-
for (const k of own) {
|
|
89
|
-
if (!seen.has(k)) out.push(k)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return out
|
|
93
|
-
}
|