parse method

  1. @override
Map<String, dynamic> parse(
  1. String source
)
override

Parses the given source string into a Map<String, dynamic>.

Implementations must:

  • Convert the raw string into structured key-value pairs.
  • Return a valid map representation.
  • Throw a FormatException if parsing fails due to invalid syntax or unexpected input.

Example:

final config = parser.parse('{"debug": true}');
print(config['debug']); // true

Implementation

@override
Map<String, dynamic> parse(String source) {
  try {
    final lines = source.split('\n');
    final root = <String, dynamic>{};

    // containerStack holds either Map<String,dynamic> or List<dynamic>
    final List<dynamic> containerStack = [root];
    final List<int> indentStack = [0];

    for (var i = 0; i < lines.length; i++) {
      var originalLine = lines[i];
      var line = originalLine.replaceFirst(RegExp(r'\r$'), '').trimRight();
      if (line.trim().isEmpty) continue;
      final trimmed = line.trim();
      if (trimmed.startsWith('#')) continue; // comment

      final indent = _getIndentation(originalLine);

      // pop containers until current indent is < stack top indent
      while (indentStack.length > 0 && indent <= indentStack.last && indentStack.length > 1) {
        indentStack.removeLast();
        containerStack.removeLast();
      }

      final currentContainer = containerStack.last;

      // List item
      if (trimmed.startsWith('-')) {
        final itemText = trimmed.length > 1 ? trimmed.substring(1).trim() : '';

        // Ensure we have a List to append to. If currentContainer is Map, attempt to attach list to last key.
        List<dynamic>? targetList;
        if (currentContainer is List) {
          targetList = currentContainer;
        } else if (currentContainer is Map<String, dynamic>) {
          if (currentContainer.isEmpty) {
            // nothing to attach list to — create an anonymous list? skip
            // This case shouldn't normally occur for well-formed YAML that uses key: followed by - items
            targetList = <dynamic>[];
            // We cannot assign to a key, so append to root? fallback:
            (containerStack.first as Map)[
                '__anonymous_list_${indentStack.length}_${i}'] = targetList;
          } else {
            // assume last key stores the list
            final lastKey = currentContainer.keys.last;
            var existing = currentContainer[lastKey];
            if (existing is! List) {
              existing = <dynamic>[];
              currentContainer[lastKey] = existing;
            }
            targetList = existing;
          }
        } else {
          // unknown container type - skip
          continue;
        }

        // Process itemText
        if (itemText.isEmpty) {
          // item is a nested map (block sequence)
          final newMap = <String, dynamic>{};
          targetList.add(newMap);
          // push newMap as current container with indent greater than '-' line
          containerStack.add(newMap);
          indentStack.add(indent + 1);
        } else if (itemText.contains(':')) {
          // inline map item: "- key: value"
          final colonIdx = itemText.indexOf(':');
          final k = itemText.substring(0, colonIdx).trim();
          final vstr = itemText.substring(colonIdx + 1).trim();
          final entryMap = <String, dynamic>{k: _parseValue(vstr)};
          targetList.add(entryMap);
        } else {
          // scalar item
          targetList.add(_parseValue(itemText));
        }

        continue;
      }

      // Key: value or key: (empty => nested structure)
      if (trimmed.contains(':')) {
        final colonIndex = trimmed.indexOf(':');
        final key = trimmed.substring(0, colonIndex).trim();
        final valueStr = trimmed.substring(colonIndex + 1).trim();

        if (currentContainer is! Map<String, dynamic>) {
          // if we are inside a List with a Map last element, use that map
          if (currentContainer is List && currentContainer.isNotEmpty && currentContainer.last is Map<String, dynamic>) {
            final parentMap = currentContainer.last as Map<String, dynamic>;
            if (valueStr.isEmpty) {
              // decide whether to create a list or map by looking ahead
              final next = _peekNextNonEmptyLine(lines, i);
              if (next != null && next.indent > indent && next.trim.startsWith('-')) {
                final list = <dynamic>[];
                parentMap[key] = list;
                containerStack.add(list);
                indentStack.add(indent + 1);
              } else {
                final nested = <String, dynamic>{};
                parentMap[key] = nested;
                containerStack.add(nested);
                indentStack.add(indent + 1);
              }
            } else {
              parentMap[key] = _parseValue(valueStr);
            }
            continue;
          } else {
            // Unexpected: current container is not a map - create a map and attach? fallback to root
            if (containerStack.first is Map<String, dynamic>) {
              (containerStack.first as Map)[key] = valueStr.isEmpty ? <String, dynamic>{} : _parseValue(valueStr);
              if (valueStr.isEmpty) {
                containerStack.add((containerStack.first as Map)[key]);
                indentStack.add(indent + 1);
              }
              continue;
            }
          }
        }

        // currentContainer is a Map<String,dynamic>
        final map = currentContainer as Map<String, dynamic>;

        if (valueStr.isEmpty) {
          // Need to look ahead to decide if this key maps to a List or Map
          final next = _peekNextNonEmptyLine(lines, i);
          if (next != null && next.indent > indent && next.trim.startsWith('-')) {
            // create a list
            final list = <dynamic>[];
            map[key] = list;
            containerStack.add(list);
            indentStack.add(indent + 1);
          } else {
            // create a nested map
            final nested = <String, dynamic>{};
            map[key] = nested;
            containerStack.add(nested);
            indentStack.add(indent + 1);
          }
        } else if (valueStr.startsWith('[') && valueStr.endsWith(']')) {
          map[key] = _parseInlineArray(valueStr);
        } else if (valueStr.startsWith('{') && valueStr.endsWith('}')) {
          map[key] = _parseInlineObject(valueStr);
        } else {
          map[key] = _parseValue(valueStr);
        }

        continue;
      }

      // If we get here: a line without ":" and not starting with "-". We treat as scalar or ignore.
      // For robustness, try to attach to last map if possible.
      if (currentContainer is Map<String, dynamic> && currentContainer.isNotEmpty) {
        final lastKey = currentContainer.keys.last;
        // append/overwrite? Usually unexpected - we set as lastKey's value if it's a Map and previously empty.
        final lastVal = currentContainer[lastKey];
        if (lastVal is Map<String, dynamic> && lastVal.isEmpty) {
          // treat this line as "key: value" missing colon; skip in strict parser
          // ignore
        } else if (lastVal is List) {
          // could be something like a list item without '-'; ignore or add
        }
      }
    }

    return root;
  } catch (e) {
    throw ParserException('Failed to parse YAML: $e');
  }
}