Conversation
…limiter spans Adds retainLayout: false (default) to loadYaml, loadYamlNode, loadYamlDocument, and Loader. When enabled: - Preserves layout elements (LayoutElement: CommentElement, WhitespaceElement, NewlineElement) in leadingLayout and trailingLayout on tokens, events, YamlNode, and YamlDocument. - Preserves delimiter source spans: YamlMap.colonSpan(key) and YamlList.dashSpan(index). - Has zero overhead and maintains 100% backwards compatibility when retainLayout is false. TAG=agy CONV=eabffc54-0045-490f-beb6-67e4668362d1
There was a problem hiding this comment.
Code Review
This pull request introduces a layout retention feature to the yaml package, allowing the parser to preserve non-semantic source layout elements (such as comments, whitespace, and newlines) as well as source spans for colons and hyphens when retainLayout is enabled. The review feedback focuses on optimizing memory usage and avoiding unnecessary allocations when retainLayout is disabled (the default). Specifically, the reviewer suggests lazily allocating trailingLayout lists on Tokens, as well as lazily initializing dashSpans and colonSpans in the Loader class only when they are actually encountered.
| final List<LayoutElement> trailingLayout; | ||
|
|
||
| Token(this.type, this.span); | ||
| Token( | ||
| this.type, | ||
| this.span, { | ||
| this.leadingLayout = const [], | ||
| List<LayoutElement>? trailingLayout, | ||
| }) : trailingLayout = trailingLayout ?? []; |
There was a problem hiding this comment.
To avoid allocating a new empty growable list [] for trailingLayout on every single token when layout retention is disabled, we can make trailingLayout non-final, default it to const [], and provide a helper method addTrailingLayout that lazily allocates the growable list only when a trailing layout element is actually added.
| final List<LayoutElement> trailingLayout; | |
| Token(this.type, this.span); | |
| Token( | |
| this.type, | |
| this.span, { | |
| this.leadingLayout = const [], | |
| List<LayoutElement>? trailingLayout, | |
| }) : trailingLayout = trailingLayout ?? []; | |
| List<LayoutElement> trailingLayout; | |
| Token( | |
| this.type, | |
| this.span, { | |
| this.leadingLayout = const [], | |
| this.trailingLayout = const [], | |
| }); | |
| void addTrailingLayout(LayoutElement element) { | |
| if (identical(trailingLayout, const <LayoutElement>[])) { | |
| trailingLayout = <LayoutElement>[]; | |
| } | |
| trailingLayout.add(element); | |
| } |
| if (!afterLineBreak && _lastToken != null) { | ||
| _lastToken!.trailingLayout.add(elem); | ||
| } else { |
| if (!afterLineBreak && _lastToken != null) { | ||
| _lastToken!.trailingLayout | ||
| .add(CommentElement(commentSpan, isTrailing: true)); | ||
| } else { |
| if (_scanner.state.position > wsStart.position) { | ||
| token.trailingLayout.add(WhitespaceElement(_scanner.spanFrom(wsStart))); | ||
| } | ||
| if (_scanner.peekChar() == HASH) { | ||
| var commentStart = _scanner.state; | ||
| _skipComment(); | ||
| token.trailingLayout.add(CommentElement( | ||
| _scanner.spanFrom(commentStart), | ||
| isTrailing: true)); | ||
| } |
There was a problem hiding this comment.
Use the new addTrailingLayout helper method on Token to lazily allocate the trailingLayout list only when needed.
if (_scanner.state.position > wsStart.position) {
token.addTrailingLayout(WhitespaceElement(_scanner.spanFrom(wsStart)));
}
if (_scanner.peekChar() == HASH) {
var commentStart = _scanner.state;
_skipComment();
token.addTrailingLayout(CommentElement(
_scanner.spanFrom(commentStart),
isTrailing: true));
}| var children = <YamlNode>[]; | ||
| var dashSpans = <SourceSpan?>[]; |
There was a problem hiding this comment.
| while (event.type != EventType.sequenceEnd) { | ||
| dashSpans.add(_parser.lastDashSpan); | ||
| children.add(_loadNode(event)); | ||
| event = _parser.parse(); | ||
| } |
There was a problem hiding this comment.
Lazily populate dashSpans only if we encounter a non-null dash span, padding with nulls up to the current index if necessary.
while (event.type != EventType.sequenceEnd) {
var dashSpan = _parser.lastDashSpan;
if (dashSpan != null) {
dashSpans ??= List<SourceSpan?>.filled(children.length, null, growable: true);
dashSpans.add(dashSpan);
} else if (dashSpans != null) {
dashSpans.add(null);
}
children.add(_loadNode(event));
event = _parser.parse();
}| if (dashSpans.any((s) => s != null)) { | ||
| setDashSpans(node, dashSpans); | ||
| } |
| var children = deepEqualsMap<dynamic, YamlNode>(); | ||
| var colonSpans = deepEqualsMap<dynamic, SourceSpan>(); |
There was a problem hiding this comment.
To ensure zero extra allocations when retainLayout is false, lazily initialize colonSpans only when a non-null colon span is actually encountered.
| var children = deepEqualsMap<dynamic, YamlNode>(); | |
| var colonSpans = deepEqualsMap<dynamic, SourceSpan>(); | |
| var children = deepEqualsMap<dynamic, YamlNode>(); | |
| Map<dynamic, SourceSpan>? colonSpans; |
| var colonSpan = _parser.lastColonSpan; | ||
| if (colonSpan != null) { | ||
| colonSpans[key] = colonSpan; | ||
| } |
There was a problem hiding this comment.
Lazily allocate the colonSpans map only when a non-null colon span is encountered.
| var colonSpan = _parser.lastColonSpan; | |
| if (colonSpan != null) { | |
| colonSpans[key] = colonSpan; | |
| } | |
| var colonSpan = _parser.lastColonSpan; | |
| if (colonSpan != null) { | |
| colonSpans ??= deepEqualsMap<dynamic, SourceSpan>(); | |
| colonSpans[key] = colonSpan; | |
| } |
| if (colonSpans.isNotEmpty) { | ||
| setColonSpans(node, colonSpans); | ||
| } |
cst.dart was an exploratory prototype for a standalone CST with trivia definitions. Now that layout retention (LayoutElement, colonSpan, dashSpan) is provided directly by package:yaml (PR dart-lang#2595), this file is completely unused and redundant. TAG=agy CONV=eabffc54-0045-490f-beb6-67e4668362d1
…t retention - Add anchorSpan, aliasSpan, entrySpan, commaSpan, openSpan, closeSpan, startMarkerSpan, and endMarkerSpan accessors to YamlNode, YamlMap, YamlList, and YamlDocument. - In Loader, construct entry spans for mapping and sequence items preserving leading comments and indentation trivia. - In YamlMapWrapper and YamlListWrapper, forward layout span accessors. - Add comprehensive tests in test/layout_test.dart. TAG=agy CONV=eabffc54-0045-490f-beb6-67e4668362d1
|
Superseded by and merged into unified monorepo PR #2593. |
Overview
This PR adds optional layout retention to
package:yamlwithout impacting existing parsing performance or backwards compatibility when disabled.When
retainLayout: trueis passed toloadYaml,loadYamlNode,loadYamlDocument,loadYamlStream, orLoader:LayoutElement:CommentElement,WhitespaceElement,NewlineElement) inleadingLayoutandtrailingLayouton tokens, events,YamlNode, andYamlDocument.CommentElement.isTrailing: true) from full-line/preceding comments (CommentElement.isTrailing: false).YamlMap.colonSpan(key): theSourceSpanof the association colon (:) for mapping entries.YamlList.dashSpan(index): theSourceSpanof the sequence hyphen (-) for sequence entries.LayoutElementhierarchy.When
retainLayout: false(default):package:yamlpass without modification.TAG=agy
CONV=eabffc54-0045-490f-beb6-67e4668362d1