Skip to content

Sec 41 - #166

Merged
jenspapenhagen merged 13 commits into
toon-format:mainfrom
jenspapenhagen:sec41
Jul 31, 2026
Merged

Sec 41#166
jenspapenhagen merged 13 commits into
toon-format:mainfrom
jenspapenhagen:sec41

Conversation

@jenspapenhagen

@jenspapenhagen jenspapenhagen commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Linked Issue

Closes #

Description

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test coverage improvement

Changes Made

SPEC Compliance

  • This PR implements/fixes spec compliance
  • Spec section(s) affected:
  • Spec version: 4.1

Testing

  • All existing tests pass
  • Added new tests for changes
  • Tests cover edge cases and spec compliance

Pre-submission Checklist

  • My code follows the project's coding standards
  • I have run code formatting/linting tools
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally
  • I have updated documentation if needed
  • I have reviewed the TOON specification for relevant sections

Breaking Changes

  • No breaking changes
  • Breaking changes (describe migration path below)

Additional Context

@jenspapenhagen
jenspapenhagen requested a review from a team as a code owner July 31, 2026 20:08
Encoder: detects objects of uniform objects (>= 2 entries, uniform columns incl. nested field groups), emits keyed headers 'key[N:<delim?>]{fields}:' with entry rows 'entrykey: c1,c2…' in first-entry field order; keyless root form '[N:]{fields}:' (§5); keyed header on the hyphen line for the first field of a list item (§10); entry keys per §7.3, cells quoted per §7.

Decoder: scan-based keyed-header matcher in Headers (balance-counting, quoted keys, optional delimiter after the keyed marker), KeyedObjectDecoder with strict entry-count/duplicate checks and inline-content guard, keyed dispatch in KeyDecoder/ObjectDecoder/ListItemDecoder/ValueDecoder; hyphen-line keyed entries at document depth + 3.

Also drops the single-row tabular exemption: size-1 arrays with uniform columns now use tabular form per §9.3 (updates JToonTest expectation).

support nested field groups in tabular arrays (§9.3)

Decoder: parse header field lists as a tree (FieldNode) and assign
cells depth-first pre-order; match keyed array headers with a
balanced-brace scanner instead of the flat regex.

Encoder: derive a TabularField tree for uniform nested object columns,
format nested field groups in headers, and write row cells depth-first.

read indentSize option from conformance fixtures

The conformance harness mapped only 'indent', but spec 4.1 fixtures use
'indentSize'; the option was silently ignored, so custom-indent tests ran
with the default indent of 2. Rename the model fields accordingly.

 enforce header validity, keyed tabular and fatal-scope rules (§5, §6, §7, §9)

- restrict unquoted keyed-array keys to non-whitespace, non-colon tokens (§7.3)
- reject keyless array headers outside document root / list-item plain form (§5, §6)
- reject duplicate field names in strict-mode tabular headers (§9.3)
- fall back to key-value lines on tabular delimiter mismatch in non-strict mode (§6)
- throw fatal on bare token lines and characters after a closing quote in both
  strict and non-strict mode (§5.2, §7.4) via new FatalDecodeException
- conformance failures: 56 -> 43

strip full-line comments before structural interpretation (§5.1)

- ValueDecoder.decodeInternal: a line whose first non-space character
  is '#' (U+0020 only) is a full-line comment and is discarded before
  any structural interpretation (D1).
- Only U+0020 spaces may precede '#': a tab before '#' disqualifies
  the line, and '#' anywhere else (quoted values, list items, after a
  colon, mid-value) is data, not a comment.
- Comments are exempt from the strict indentation check and do not
  count as rows, items, or scope terminators.
- A document of only comments and blank lines decodes to an empty
  object.

feat(spec-4.1): enforce scope rules, blank-line and list-item handling (§5, §9, §12, §14)

Decoder:
- ValueDecoder: strip trailing spaces per line (§12); quoted strings
  keep their trailing spaces. Reject leftover content after the root
  form (D7, §5/§5.2): any unconsumed line errors in strict mode,
  bare-token lines error in both modes.
- DecodeHelper: add validateNoTrailingContent() implementing the
  root-span rule.
- ArrayDecoder: accept blank lines between header and first item
  (§12); classify a bare "-" (no trailing space) as a list item
  (D11, §9.2); accept "[]" as an empty array header (§9.1/§9.2);
  array length validation now applies in strict mode only, the
  declared length never truncates a scope (D13, §14.1).
- TabularArrayDecoder: accept blank lines between header and first
  row (§12) and trailing blank lines at end of file (CRLF/§12);
  over-indented lines after rows throw in strict mode (§14.2).
- ObjectDecoder/KeyDecoder: inline-primitive fields do not open a
  scope - a deeper following line is over-indentation and throws in
  strict mode (§14.2); skipped in lenient mode (D6).
- ListItemDecoder: over-indented field lines throw in strict mode
  (§14.2).

Tests:
- Update legacy unit tests that asserted the pre-4.1 "silently skip
  over-indented lines in strict mode" behavior to run in lenient
  mode, where skipping remains the correct behavior.

 align quoting rules and number grammar with spec 4.1

- Encoder (StringValidator, spec §7.2/§7.3):
  - quote strings equal to '#' or starting with '#' (comment marker)
  - treat a leading '+' as numeric-like so '+1' is quoted
  - restrict unquoted keys to ASCII ^[A-Za-z_][A-Za-z0-9_.]*$, quoting
    non-ASCII keys like 'café' or '名前'
- Decoder (PrimitiveDecoder, spec §4):
  - enforce the normative number grammar ^-?[0-9]+(?:\.[0-9]+)?(?:e[+-]?[0-9]+)?$
    instead of delegating to Java's wider number parsers; tokens like
    '.5', '1.', '+1' or 'NaN' now decode as strings
- Decoder (ValueDecoder, spec §5.1):
  - strip a single leading U+FEFF byte-order mark before any processing
- build.gradle: nullaway 0.13.7 -> 0.13.8
- gradle/verification-metadata.xml: regenerate 0.13.8 sha256 checksums, drop 0.13.7 block
- ArrayDecoder: extract matcher group index magic number to FIELDS_GROUP_INDEX, literal-first equals
- PrimitiveDecoder: group static imports with java.util imports
- TabularArrayDecoder: convert FieldNode class to record, varargs cell cursor, avoid assignment-in-operand
- TabularArrayEncoder/KeyedObjectEncoder: uniformColumnsOf returns Optional instead of @nullable list
- ValueDecoder: replace escaped unicode BOM literal with BOM_CHARACTER constant
- KeyDecoder/ObjectDecoder: wrap overlong parseKeyedTabularObject lines
- KeyDecoderTest: fix indentation and line length violations
split parseFieldList state machine into focused helpers
split parseKeyedTabularObject into per-validity helpers
split parseListItem and parseListItemFields into focused helpers
share nested-scope parsing between field and key values
split parseArrayWithDelimiter and parseDelimitedValues
split decodeInternal into focused root-parse helpers
split unicode escape validation and decoding into helpers
split scanHeader into phase-scoped scanner helpers
@jenspapenhagen
jenspapenhagen merged commit bc184de into toon-format:main Jul 31, 2026
1 check passed
@jenspapenhagen
jenspapenhagen deleted the sec41 branch July 31, 2026 20:40
@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage

Overall Project 96.92% -1.75% 🍏
Files changed 96.08% 🍏

File Coverage
FatalDecodeException.java 100% 🍏
ListItemEncoder.java 100% 🍏
TabularField.java 100% 🍏
ValueEncoder.java 100% 🍏
JsonNormalizer.java 100% 🍏
StringEscaper.java 100% 🍏
ObjectEncoder.java 99.34% -0.44% 🍏
TabularArrayDecoder.java 99.3% 🍏
TabularArrayEncoder.java 98.84% -0.58% 🍏
DecodeHelper.java 98.51% -1.49% 🍏
StringValidator.java 98.35% 🍏
HeaderFormatter.java 98.19% -1.81% 🍏
PrimitiveEncoder.java 98.11% 🍏
Headers.java 97.93% -2.07% 🍏
ValueDecoder.java 97.35% -0.26% 🍏
ArrayEncoder.java 96.76% 🍏
ArrayDecoder.java 96.21% -2.78% 🍏
KeyedObjectEncoder.java 95.45% -4.55% 🍏
ListItemDecoder.java 95.44% -3.54% 🍏
PrimitiveDecoder.java 95.41% 🍏
KeyedObjectDecoder.java 94.01% -5.99% 🍏
ObjectDecoder.java 90.4% -7.29% 🍏
KeyDecoder.java 86.78% -7.78% 🍏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant