jmd-format 0.1.0 → 0.1.1

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
@@ -94,6 +94,11 @@ string chunks into an async iterable of complete lines.
94
94
  - Thematic breaks (`---`) as array-item separators (§8.6). Consumed by
95
95
  the innermost enclosing array whose most-recent item is a dict with
96
96
  nested structures.
97
+ - Depth-qualified array items (`##N -`, §8.6a) and depth+1 items
98
+ (`##N - key: val`, §8.6b). Parser-tolerance forms: the canonical
99
+ serializer emits bare `- ` items with thematic-break separators, but
100
+ the parser accepts the depth-annotated forms that LLMs tend to
101
+ produce when items sit one heading level below the array heading.
97
102
  - **Streaming parser** via async generator: events match the sequence
98
103
  defined in §18.2 (document_start, field, field_start, field_content,
99
104
  object_start/end, array_start/end, item_start/value/end, scope_reset,
@@ -101,14 +106,11 @@ string chunks into an async iterable of complete lines.
101
106
  - **Streaming serializer** via sync generator (`serializeLines`).
102
107
  - Line adapter (`toLines`) to convert chunked input to lines.
103
108
 
104
- ## Not yet supported
109
+ ## Not yet structured
105
110
 
106
- Planned for subsequent releases; parser throws a clear error if encountered.
107
-
108
- - Depth-qualified items (`## -`, `### -`).
109
- - Depth+1 items (items one heading level deeper than the array heading).
110
- - Schema-specific type expressions, QBE filter conditions — parsed as raw
111
- strings today; structured interpretation will follow.
111
+ Schema-specific type expressions (§14) and QBE filter conditions (§13)
112
+ are parsed as raw strings on both this implementation and the Python
113
+ reference. Structured interpretation will follow in a later release.
112
114
 
113
115
  ## Design
114
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jmd-format",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "JMD (JSON Markdown) — structured data format for LLM-driven infrastructure. JavaScript reference implementation.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/parser.js CHANGED
@@ -206,6 +206,15 @@ export function createParser() {
206
206
  throw parseError('Mode markers (!, ?, -) are only valid on the root heading')
207
207
  }
208
208
 
209
+ // Depth-qualified array item (§8.6a) or depth+1 item (§8.6b):
210
+ // `##N -` or `##N - key: val` starts a new item in an enclosing array.
211
+ // Resolution prefers the innermost array at depth N; failing that, an
212
+ // array at depth N-1 (the LLM-natural "items under the heading" form).
213
+ if (text === '-' || text.startsWith('- ')) {
214
+ onDepthQualifiedItem(depth, text)
215
+ return
216
+ }
217
+
209
218
  popToDepth(depth)
210
219
 
211
220
  if (text === '' || text === undefined) {
@@ -213,11 +222,6 @@ export function createParser() {
213
222
  return
214
223
  }
215
224
 
216
- // Depth-qualified array item: `## -` or `## - key: val` — deferred.
217
- if (text === '-' || text.startsWith('- ')) {
218
- throw parseError('Depth-qualified array items (## -) are not yet supported')
219
- }
220
-
221
225
  // Anonymous sub-array: `### []` — handled below with the other array forms.
222
226
  if (text === '[]') {
223
227
  openSubArray(depth)
@@ -356,6 +360,31 @@ export function createParser() {
356
360
 
357
361
  // --- Array items ---------------------------------------------------------
358
362
 
363
+ function onDepthQualifiedItem(headingDepth, text) {
364
+ // Find target: innermost array at depth == headingDepth wins (§8.6a).
365
+ // Else fall back to array at depth == headingDepth - 1 (§8.6b — the
366
+ // LLM-natural pattern of writing items one heading-level under the
367
+ // array heading).
368
+ let sameDepthIdx = -1
369
+ let parentDepthIdx = -1
370
+ for (let i = stack.length - 1; i >= 0; i--) {
371
+ const s = stack[i]
372
+ if (s.kind !== 'array') continue
373
+ if (sameDepthIdx === -1 && s.depth === headingDepth) sameDepthIdx = i
374
+ if (parentDepthIdx === -1 && s.depth === headingDepth - 1) {
375
+ parentDepthIdx = i
376
+ }
377
+ }
378
+ const targetIdx = sameDepthIdx !== -1 ? sameDepthIdx : parentDepthIdx
379
+ if (targetIdx === -1) {
380
+ throw parseError('Depth-qualified item has no matching array scope')
381
+ }
382
+ // Close any scopes nested inside the target array.
383
+ while (stack.length - 1 > targetIdx) popScope()
384
+ // Reuse the regular item handler with the target array on top.
385
+ onItem(text)
386
+ }
387
+
359
388
  function onItem(line) {
360
389
  const top = stack[stack.length - 1]
361
390
  if (top.kind !== 'array') throw parseError('Array item outside array scope')