Skip to content

perf(parser): defer segment field parsing until first access - #40

Closed
shadowhand wants to merge 2 commits into
mainfrom
feature/sv-77-defer-segment-field-parsing-until-first-access-lazy
Closed

shadowhand wants to merge 2 commits into
mainfrom
feature/sv-77-defer-segment-field-parsing-until-first-access-lazy

Conversation

@shadowhand

Copy link
Copy Markdown
Contributor

Problem

MessageFactory::parse() eagerly parses every field of every segment, whether or not the caller reads it. After SV-76 the remaining cost is field hydration: on a 7.9 KB ADT_A01 with 100 NK1, parse() with nothing read was about 5 ms and retained about 3.3 MB for 7.9 KB of input. Serializing an unread message then re-encoded that whole graph.

Change

Segments bind the raw line and the parse-time delimiters instead of parsing fields up front. getField(), getFieldRepetition(), getLength(), and serialize() hydrate on first use. Schema queries (getNames(), getFieldCount(), isRequired(), maxRepetitions()) still read only the definition table.

serialize() emits the bound line verbatim when no field has been written and the target delimiters match. The line ending is not part of a segment line, so changing it only changes the join. A write (setValue(), clear(), composite parse(), or Varies::setData()) drops that segment's line; only the touched segment is re-encoded, and trailing empty fields are still trimmed. Hand-built segments have no raw line and serialize as before.

debug() reads one field before counting, so an undeclared segment still shows its wire fields. The routing MSH in MessageFactory is still parsed immediately, because the message type is needed to choose the message class.

Verification

vendor/bin/phpunit — 852 tests, 2663 assertions, coverage 100%. mago format --check, mago lint, and mago analyze report no issues.

Refs SV-77

Keep each segment's raw line until a field is written so unread messages stay small and a read-only round trip is byte-exact.
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The Robin Hood of code review

✅ Review done. I flagged 2 things worth a look.

Push fixes whenever you like, then comment /robin for another pass.

Robin v2.12.0 · antongulin/robin@main (fc0bf91) · agent review: 5 turns · 25 tool calls

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Robin Hood of code review

Heads up: this is a point-in-time review. Push fixes freely, then comment /robin whenever you want another pass.

⚠️ 2 Medium

Summary

The change adds lazy segment hydration and preserves untouched wire lines, with useful coverage for read-only round trips and mutation tracking. Two edge cases remain: recovery no longer preserves custom Segment implementations that do not extend AbstractSegment, and a failed lazy hydration can discard the original wire line.

Comment thread src/AbstractGroup.php
Comment thread src/AbstractSegment.php Outdated
A caught parse error must leave the bound line in place and the segment unhydrated so a later read can try again.
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The Robin Hood of code review

✅ Review done. I flagged 2 things worth a look.

Push fixes whenever you like, then comment /robin for another pass.

Robin v2.12.0 · antongulin/robin@main (fc0bf91) · agent review: 4 turns · 21 tool calls

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Robin Hood of code review

Heads up: this is a point-in-time review. Push fixes freely, then comment /robin whenever you want another pass.

⚠️ 2 Medium

Summary

The change adds lazy segment hydration and preserves untouched wire lines, with focused tests covering round trips, reads, and writes. Two compatibility gaps remain: mutable custom Type implementations can leave the raw line stale, and out-of-order recovery no longer preserves custom segments that do not extend AbstractSegment.

Comment thread src/TypeDefinition.php
Comment thread src/AbstractGroup.php
@shadowhand
shadowhand marked this pull request as ready for review October 17, 2026 11:13
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The Robin Hood of code review

✅ Review done. I flagged 2 things worth a look.

Push fixes whenever you like, then comment /robin for another pass.

Robin v2.12.0 · antongulin/robin@main (fc0bf91) · agent review: 6 turns · 31 tool calls

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Robin Hood of code review

Heads up: this is a point-in-time review. Push fixes freely, then comment /robin whenever you want another pass.

⚠️ 2 Medium

Summary

The change adds lazy segment hydration and mutation tracking to preserve untouched wire lines, with focused tests for round trips, delimiter changes, and writes. Two compatibility regressions remain: out-of-order recovery no longer preserves custom Segment implementations, and parsing now bypasses existing AbstractSegment subclasses that override the public parse method.

Comment thread src/AbstractGroup.php
// A group lead out of order is unsafe to re-consume, so only segments recover typed.
if ($structure instanceof Segment) {
$structure->parse($encoding, $element->raw);
if ($structure instanceof AbstractSegment) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robin — ⚠️ MEDIUM · confidence: high

Out-of-order recovery previously retained any structure implementing Segment as its declared type. Restricting this to AbstractSegment sends custom Segment implementations through the GenericSegment fallback, losing their typed behavior and schema when a declared segment appears out of order.

Recommendation: Preserve the Segment interface behavior during recovery; use the segment's parse method for implementations that cannot bind a raw line, or provide a compatible lazy-binding contract.

Comment thread src/AbstractSegment.php
);

$this->parse($encoding, $element->raw);
$this->bind($encoding, $element->raw);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robin — ⚠️ MEDIUM · confidence: high

parseSegments previously called the virtual parse() method. Binding instead means that lazy hydration calls populate(), so existing AbstractSegment subclasses that override the public parse() method are silently bypassed when parsed through a message or group.

Recommendation: Provide a migration-compatible hydration hook for custom parsing behavior, or otherwise ensure subclasses overriding parse() continue to participate in the parseSegments path.

@shadowhand

Copy link
Copy Markdown
Contributor Author

Superseded: SV-77 is being redesigned around field-level lazy parsing with comparison-based change detection instead of a dirty flag.

@shadowhand shadowhand closed this Oct 17, 2026
@shadowhand
shadowhand deleted the feature/sv-77-defer-segment-field-parsing-until-first-access-lazy branch October 17, 2026 11:38
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