FlcData.fromJson constructor

FlcData.fromJson(
  1. Map<String, dynamic> json
)

Parse FLC data from a v5/v6 trie-based hierarchy JSON.

v5 and v6 share the trie structure; v6 adds multi-position support (comma-separated entries per language) and list-per-language outputFiles. Any other version is rejected and returns empty FLC data.

Implementation

factory FlcData.fromJson(Map<String, dynamic> json) {
  final version = json['version'];
  if (version != null && version != 5 && version != 6) {
    return FlcData._(files: [], signals: {}, instances: {});
  }
  final files =
      (json['files'] as List<dynamic>?)?.cast<String>() ?? <String>[];
  final rawModules = json['modules'];
  final modules =
      rawModules is Map ? Map<String, dynamic>.from(rawModules) : null;
  if (modules == null) {
    return FlcData._(files: files, signals: {}, instances: {});
  }

  final signals = <String, Map<String, FlcEntry>>{};
  final instances = <String, Map<String, FlcEntry>>{};

  for (final modEntry in modules.entries) {
    final moduleName = modEntry.key;
    final modMap = modEntry.value as Map<String, dynamic>?;
    if (modMap == null) {
      continue;
    }

    final svFile = modMap['svFile'] as String?;
    // Output files map.
    //   v5: {"sv": "Foo.sv", "sc": "Foo.h"}        (string per language)
    //   v6: {"sv": ["Foo.sv"], "sc": ["Foo.h"]}    (list per language)
    // Falls back to legacy "svFile" field. For lookup we use the first
    // file in each language list (the canonical output).
    final rawOutputFiles = modMap['outputFiles'];
    final outputFiles = <String, String>{if (svFile != null) 'sv': svFile};
    if (rawOutputFiles is Map) {
      for (final e in rawOutputFiles.entries) {
        final v = e.value;
        if (v is String) {
          outputFiles[e.key as String] = v;
        } else if (v is List && v.isNotEmpty && v.first is String) {
          outputFiles[e.key as String] = v.first as String;
        }
      }
    }
    final tree = modMap['tree'] as List<dynamic>?;
    if (tree == null) {
      continue;
    }

    final modSignals = <String, FlcEntry>{};
    final modInstances = <String, FlcEntry>{};

    /// Walk a trie node, collecting frames along the path.
    /// Frames are accumulated outermost-first; we reverse at the leaf
    /// to match the innermost-first convention of FlcEntry.frames.
    void walkNode(List<dynamic> node, List<String> path) {
      if (node.isEmpty) {
        return;
      }
      final frame = node[0] as String;
      final currentPath = [...path, frame];

      for (var i = 1; i < node.length; i++) {
        final elem = node[i];
        if (elem is List) {
          // Child trie node.
          walkNode(elem.cast<dynamic>(), currentPath);
        } else if (elem is String) {
          // String-encoded leaf symbol.
          final parsed = _parseSymbolString(elem);
          final name = parsed.name;
          final isInstance = parsed.isInstance;
          final origName = parsed.origName;

          // Build ROHD source frames (reverse to innermost-first).
          final rohdFrames = <FlcFrame>[];
          for (final f in currentPath.reversed) {
            final parts = f.split(':');
            if (parts.length < 2) {
              continue;
            }
            final fi = int.tryParse(parts[0]);
            if (fi == null || fi >= files.length) {
              continue;
            }
            final line = int.tryParse(parts[1]) ?? 1;
            final col = parts.length > 2 ? (int.tryParse(parts[2]) ?? 1) : 1;
            rohdFrames.add(
              FlcFrame(file: files[fi], line: line, column: col),
            );
          }

          // Build output-language frames from parsed positions.
          final outFrames = <FlcFrame>[];
          for (final pos in parsed.outputPositions) {
            final file = outputFiles[pos.type];
            if (file == null) {
              continue;
            }
            outFrames.add(
              FlcFrame(
                file: file,
                line: pos.line,
                column: pos.column,
                type: pos.type,
              ),
            );
          }

          if (rohdFrames.isNotEmpty || outFrames.isNotEmpty) {
            final entry = FlcEntry(
              frames: rohdFrames,
              outputFrames: outFrames,
              origName: origName,
            );
            if (isInstance) {
              modInstances[name] = entry;
            } else {
              modSignals[name] = entry;
            }
          }
        }
      }
    }

    // tree is a list of root trie nodes.
    for (final rootNode in tree) {
      if (rootNode is List) {
        walkNode(rootNode.cast<dynamic>(), []);
      }
    }

    if (modSignals.isNotEmpty) {
      signals[moduleName] = modSignals;
    }
    if (modInstances.isNotEmpty) {
      instances[moduleName] = modInstances;
    }
  }

  return FlcData._(files: files, signals: signals, instances: instances);
}