toSExpression method
Native ts_subtree_string representation.
includeAll corresponds to the native helper's internal include_all
flag. The ordinary public tree/node string path leaves it false.
Implementation
String toSExpression({bool includeAll = false}) {
final frames = <_GeneratedSExpressionFrame>[
_GeneratedSExpressionFrame(this, root: true, includeAll: includeAll),
];
var result = '';
while (frames.isNotEmpty) {
final frame = frames.last;
final node = frame.node;
if (frame.nextChild < node.children.length) {
final child = node.children[frame.nextChild++];
String? childFieldName;
if (!child.extra) {
childFieldName = frame.isVisible ? null : frame.fieldName;
for (final field in node.productionFields) {
if (!field.inherited && field.childIndex == frame.structuralIndex) {
childFieldName = field.name;
break;
}
}
if (childFieldName == null) {
final directFields = child.fields.where(
(field) => !field.inherited,
);
if (directFields.isNotEmpty) {
childFieldName = directFields.first.name;
}
}
frame.structuralIndex++;
}
frames.add(
_GeneratedSExpressionFrame(
child,
fieldName: childFieldName,
root: !frame.isVisible && frame.root,
includeAll: includeAll,
),
);
continue;
}
final unexpected = node.unexpectedCharacter;
String? head;
if (unexpected != null) {
final value = switch (unexpected) {
-1 => 'INVALID',
0 => r"'\0'",
0x0a => r"'\n'",
0x09 => r"'\t'",
0x0d => r"'\r'",
>= 0x20 && < 0x7f => "'${String.fromCharCode(unexpected)}'",
_ => '$unexpected',
};
head = '(UNEXPECTED $value';
} else if (node.missing) {
final name = node.missingNameIsNamed
? node.type
: jsonEncode(node.type);
head = '(MISSING $name';
} else if (frame.isVisible) {
head = '(${node.type}';
}
String value;
if (frame.isVisible) {
value =
'${frame.root || frame.fieldName == null ? '' : '${frame.fieldName}: '}'
'$head${frame.children.isEmpty ? '' : ' ${frame.children.join(' ')}'})';
} else if (frame.root) {
if (node.children.isNotEmpty) {
// This intentionally mirrors the hidden-root branch in
// `ts_subtree__write_to_string`, including its lack of a closing
// parenthesis when the hidden root has children.
value = '(${node.type}${frame.children.join()}';
} else if (node.named) {
value = '(${node.type})';
} else {
value = '(${jsonEncode(node.type)})';
}
} else {
value = frame.children.join(' ');
}
frames.removeLast();
if (frames.isEmpty) {
result = value;
} else if (value.isNotEmpty) {
frames.last.children.add(value);
}
}
return result;
}