namespace EightBit { namespace Files { namespace Symbols { using System.Diagnostics; public sealed class Parser { #region Variables, properties etc. public bool Parsed { get; private set; } // Section -> Unique ID list of dictionary entries // Being sorted allows us to verify IDs as they arrive private readonly Dictionary>> _parsed = []; private Version? _version; private Information? _information; public List Files { get; } = []; public List Lines { get; } = []; public List Modules { get; } = []; public List Segments { get; } = []; public List Spans { get; } = []; public List Scopes { get; } = []; public List Symbols { get; } = []; public List Types { get; } = []; // Symbol sub-types public List Labels { get; } = []; public List Equates { get; } = []; // Value lookup structures public Dictionary> Addresses { get; } = []; public Dictionary> Constants { get; } = []; #endregion #region Lookups #region Label lookup public void AddLabel(Symbol symbol) { if (symbol.Type != "lab") { throw new ArgumentOutOfRangeException(nameof(symbol), "Not a label"); } this.Labels.Add(symbol); this.AddAddress(symbol); } private void AddAddress(Symbol symbol) { var value = symbol.Value; if (this.Addresses.TryGetValue(value, out var symbols)) { symbols.Add(symbol); } else { this.Addresses.Add(value, [symbol]); } } public List LookupLabels(int address) => this.Addresses.TryGetValue(address, out var symbols) ? symbols : []; public Symbol? LookupLabel(int address) { var labels = this.LookupLabels(address); return labels.Count > 0 ? labels[0] : null; } public List LookupLabels(string name) { var returned = new List(); foreach (var label in this.Labels) { if (label.Name == name) { returned.Add(label); } } return returned; } public Symbol? LookupLabel(string name) { var labels = this.LookupLabels(name); return labels.Count > 0 ? labels[0] : null; } #endregion #region Constant lookup public void AddEquate(Symbol symbol) { if (symbol.Type != "equ") { throw new ArgumentOutOfRangeException(nameof(symbol), "Not an equate"); } this.Equates.Add(symbol); this.AddConstant(symbol); } private void AddConstant(Symbol symbol) { var value = symbol.Value; if (this.Constants.TryGetValue(value, out var symbols)) { symbols.Add(symbol); } else { this.Constants.Add(value, [symbol]); } } public List LookupEquates(int constant) => this.Constants.TryGetValue(constant, out var symbols) ? symbols : []; public Symbol? LookupEquate(int constant) { var equates = this.LookupEquates(constant); return equates.Count > 0 ? equates[0] : null; } #endregion #region Scope lookup public Scope? LookupScope(string name) { foreach (var scope in this.Scopes) { if (scope.Name == name) { return scope; } } return null; } public Scope? LookupScope(int address) { foreach (var scope in this.Scopes) { var symbol = scope.Symbol; if (symbol != null) { var symbolAddress = symbol.Value; var size = scope.Size; if ((address >= symbolAddress) && (address < symbolAddress + size)) { return scope; } } } return null; } #endregion #region Scope evaluation public static List EvaluateScope(Scope start) { var returned = new List(); for (var current = start; current.Parent != null; current = current.Parent) { returned.Add(current); } return returned; } public static List EvaluateScope(Symbol symbol) => EvaluateScope(symbol.Scope); #endregion #region Namespace evaluation from scope public static string BuildNamespace(Symbol symbol) { var returned = string.Empty; var scopes = EvaluateScope(symbol); for (var i = scopes.Count - 1; i >= 0; i--) { var scope = scopes[i]; var name = scope.Name; Debug.Assert(name.Length > 0); returned += name; var last = i == 0; if (!last) { returned += '.'; } } return returned; } public static string PrefixNamespace(Symbol? symbol) { if (symbol is null) { return string.Empty; } var prefix = BuildNamespace(symbol); var name = symbol.Name; return string.IsNullOrEmpty(prefix) ? name : $"{prefix}.{name}"; } #endregion #region Qualified symbol lookup public bool TryGetQualifiedLabel(ushort absolute, out string label) { var symbol = this.LookupLabel(absolute); label = PrefixNamespace(symbol); return symbol != null; } public string MaybeGetQualifiedLabel(ushort absolute) => this.TryGetQualifiedLabel(absolute, out var label) ? label : string.Empty; public bool TryGetQualifiedEquate(ushort value, out string name) { var symbol = this.LookupEquate(value); name = PrefixNamespace(symbol); return symbol != null; } #endregion #endregion #region Metadata lookup private int InformationCount(string key) { if (this._information == null) { throw new InvalidOperationException("Information section has not been initialised"); } return this._information.Count(key); } private void VerifyInformationCount(string key, int extracted) { if (extracted != this.InformationCount(key)) { throw new InvalidOperationException($"Invalid symbol file format (Information/{key} section count mismatch)"); } } #endregion #region Section extractors private void ExtractFiles() => this.Extract("file", this.Files); private void ExtractLines() => this.Extract("line", this.Lines); private void ExtractModules() => this.Extract("mod", this.Modules); private void ExtractSegments() => this.Extract("seg", this.Segments); private void ExtractSpans() => this.Extract("span", this.Spans); private void ExtractScopes() => this.Extract("scope", this.Scopes); private void ExtractSymbols() => this.Extract("sym", this.Symbols); private void ExtractTypes() => this.Extract("type", this.Types); private void Extract(string key, List into) where T : IdentifiableSection, new() { if (!this._parsed.TryGetValue(key, out var parsed)) { throw new InvalidOperationException($"Debugging section: '{key}' is unavailable"); } foreach (var element in parsed) { var id = element.Key; Debug.Assert(into.Count == id); var information = element.Value; var entry = new T(); entry.Parse(this, information); into.Add(entry); } this.VerifyInformationCount(key, into.Count); } #endregion #region Parser driver public void Parse(string? path) { if (this.Parsed) { throw new InvalidOperationException("A file has already been parsed."); } if (string.IsNullOrEmpty(path)) { return; } using var reader = new StreamReader(path); while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line == null) { break; } this.ParseLine(line.Split(' ', '\t')); } this.ExtractFiles(); this.ExtractLines(); this.ExtractModules(); this.ExtractSegments(); this.ExtractSpans(); this.ExtractScopes(); this.ExtractSymbols(); this.ExtractTypes(); this.Parsed = true; } private void ParseLine(string[] elements) { Debug.Assert(elements != null); if (elements.Length != 2) { throw new InvalidOperationException("Invalid symbol file format (definition does not have section/values format)"); } var key = elements[0]; var parts = elements[1].Split(','); if (key is "version") { this._version = new Version(); this._version.Parse(this, BuildDictionary(parts)); } else if (key is "info") { this._information = new Information(); this._information.Parse(this, BuildDictionary(parts)); } else { this.Parse(key, parts); } } private void Parse(string key, string[] parts) { if (!this._parsed.TryGetValue(key, out var section)) { this._parsed[key] = []; section = this._parsed[key]; } var dictionary = BuildDictionary(parts); if (!dictionary.TryGetValue("id", out var id)) { throw new InvalidOperationException("Invalid symbol file format (definition does not have id)"); } var identifier = int.Parse(id); if (section.ContainsKey(identifier)) { throw new InvalidOperationException("Invalid symbol file format (definition id has clashed)"); } if (this._information == null) { throw new InvalidOperationException("Invalid symbol file format (info section has not been parsed)"); } var count = this._information.Count(key); if ((identifier + 1) > count) { throw new InvalidOperationException($"Invalid symbol file format (No count information available for {section})"); } section.Add(identifier, dictionary); } private static Dictionary BuildDictionary(string[] parts) { var dictionary = new Dictionary(); foreach (var part in parts) { var definition = part.Split('='); if (definition.Length != 2) { throw new InvalidOperationException("Invalid symbol file format (definition is missing equals)"); } var key = definition[0]; var value = definition[1]; dictionary[key] = value; } return dictionary; } #endregion } } } }