FlcData.fromJson constructor
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);
}