diff --git a/M6502/M6502.Symbols/File.cs b/M6502/M6502.Symbols/File.cs index 4977bed..f155fad 100644 --- a/M6502/M6502.Symbols/File.cs +++ b/M6502/M6502.Symbols/File.cs @@ -1,22 +1,16 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + // file id=0,name="sudoku.s",size=9141,mtime=0x6027C7F0,mod=0 + [Section("file", "Files")] + public sealed class File(Parser container) : NamedSection(container) { - namespace Symbols - { - // file id=0,name="sudoku.s",size=9141,mtime=0x6027C7F0,mod=0 - [Section("file", "Files")] - public sealed class File(Parser container) : NamedSection(container) - { - [SectionProperty("size")] - public int Size { get; private set; } + [SectionProperty("size")] + public int Size { get; private set; } - [SectionProperty("mtime")] - public DateTime ModificationTime { get; private set; } + [SectionProperty("mtime")] + public DateTime ModificationTime { get; private set; } - [SectionReference("mod")] - public Symbols.Module? Module { get; private set; } - } - } + [SectionReference("mod")] + public Symbols.Module? Module { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/IdentifiableSection.cs b/M6502/M6502.Symbols/IdentifiableSection.cs index c52edf3..cde3e65 100644 --- a/M6502/M6502.Symbols/IdentifiableSection.cs +++ b/M6502/M6502.Symbols/IdentifiableSection.cs @@ -1,153 +1,147 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + using System; + using System.Collections; + using System.Diagnostics; + + public class IdentifiableSection : Section { - namespace Symbols + [SectionProperty("id")] + public int ID { get; protected set; } + + protected IdentifiableSection(Parser container) + : base(container) + { } + + private bool MaybeExtractFromParsed(string key, out string value) { - using System; - using System.Collections; - using System.Diagnostics; - - public class IdentifiableSection : Section + Debug.Assert(this._parsed != null); + var found = this._parsed.TryGetValue(key, out var extracted); + if (found) { - [SectionProperty("id")] - public int ID { get; protected set; } + Debug.Assert(extracted != null); + value = extracted; + } + else + { + value = string.Empty; + } + return found; + } - protected IdentifiableSection(Parser container) - : base(container) - {} + private bool MaybeExtractIntegerFromParsed(string key, out int value) + { + var available = this.MaybeExtractFromParsed(key, out var extracted); + if (!available) + { + value = 0; + return false; + } + value = ExtractInteger(extracted); + return available; + } - private bool MaybeExtractFromParsed(string key, out string value) + protected bool MaybeExtractCompoundInteger(string key, out List value) + { + var available = this.MaybeExtractFromParsed(key, out var extracted); + if (!available) + { + value = []; + return false; + } + value = ExtractCompoundInteger(extracted); + return available; + } + + private void SetProperty(string name, object obj) + { + var type = this.GetType(); + var property = type.GetProperty(name); + Debug.Assert(property != null); + property.SetValue(this, obj); + } + + public void ExtractReferences() + { + this.ExtractReferenceProperties(); + this.ExtractReferencesProperties(); + } + + private void ExtractReferenceProperties() + { + foreach (var (entryName, connection) in this.SectionProperties.ReferenceAttributes) + { + var hasID = this.MaybeExtractIntegerFromParsed(entryName, out var id); + if (hasID) { - Debug.Assert(this._parsed != null); - var found = this._parsed.TryGetValue(key, out var extracted); - if (found) - { - Debug.Assert(extracted != null); - value = extracted; - } - else - { - value = string.Empty; - } - return found; - } - - private bool MaybeExtractIntegerFromParsed(string key, out int value) - { - var available = this.MaybeExtractFromParsed(key, out var extracted); - if (!available) - { - value = 0; - return false; - } - value = ExtractInteger(extracted); - return available; - } - - protected bool MaybeExtractCompoundInteger(string key, out List value) - { - var available = this.MaybeExtractFromParsed(key, out var extracted); - if (!available) - { - value = []; - return false; - } - value = ExtractCompoundInteger(extracted); - return available; - } - - private void SetProperty(string name, object obj) - { - var type = this.GetType(); - var property = type.GetProperty(name); - Debug.Assert(property != null); - property.SetValue(this, obj); - } - - public void ExtractReferences() - { - this.ExtractReferenceProperties(); - this.ExtractReferencesProperties(); - } - - private void ExtractReferenceProperties() - { - foreach (var (entryName, connection) in this.SectionProperties.ReferenceAttributes) - { - var hasID = this.MaybeExtractIntegerFromParsed(entryName, out var id); - if (hasID) - { - var (name, type) = connection; - this.ExtractReferenceProperty(id, name, type); - } - } - } - - private void ExtractReferenceProperty(int id, string name, System.Type type) - { - // The reference container in the parent class - var referenceSectionProperties = GetSectionProperties(type); - var referenceClassAttribute = referenceSectionProperties.ClassAttribute; - Debug.Assert(referenceClassAttribute != null); - var referencingContainer = referenceClassAttribute.Referencing; - - // Get the parent container field - var containerType = this._container.GetType(); - Debug.Assert(referencingContainer != null); - var containerField = containerType.GetField(referencingContainer); - Debug.Assert(containerField != null); - var fieldValue = containerField.GetValue(this._container); - var fieldList = fieldValue as IList; - - // Now get the referenced object from the parent container field (via ID as an index) - Debug.Assert(fieldList != null); - var referencingObject = fieldList[id]; - - // Now set our reference field - Debug.Assert(referencingObject != null); - this.SetProperty(name, referencingObject); - } - - private void ExtractReferencesProperties() - { - foreach (var (entryName, connection) in this.SectionProperties.ReferencesAttributes) - { - // this'll be a set of ids - var hasIDs = this.MaybeExtractCompoundInteger(entryName, out var ids); - if (hasIDs) - { - var (name, type) = connection; - this.ExtractReferencesProperty(ids, name, type); - } - } - } - - private void ExtractReferencesProperty(List ids, string name, System.Type type) - { - // The reference container in the parent class - //var referenceSectionProperties = GetSectionProperties(type); - //var referenceClassAttribute = referenceSectionProperties.ClassAttribute; - //Debug.Assert(referenceClassAttribute != null); - //var referencingContainer = referenceClassAttribute.Referencing; - - // Get the parent container field - //var containerType = this._container.GetType(); - //Debug.Assert(referencingContainer != null); - //var containerField = containerType.GetField(referencingContainer); - //Debug.Assert(containerField != null); - //var fieldValue = containerField.GetValue(this._container); - //var fieldList = fieldValue as IList; - - // Now get the referenced object from the parent container field (via ID as an index) - //Debug.Assert(fieldList != null); - //var referencingObject = fieldList[id]; - - //// Now set our reference field - //Debug.Assert(referencingObject != null); - //this.SetProperty(name, referencingObject); + var (name, type) = connection; + this.ExtractReferenceProperty(id, name, type); } } } + + private void ExtractReferenceProperty(int id, string name, System.Type type) + { + // The reference container in the parent class + var referenceSectionProperties = GetSectionProperties(type); + var referenceClassAttribute = referenceSectionProperties.ClassAttribute; + Debug.Assert(referenceClassAttribute != null); + var referencingContainer = referenceClassAttribute.Referencing; + + // Get the parent container field + var containerType = this._container.GetType(); + Debug.Assert(referencingContainer != null); + var containerField = containerType.GetField(referencingContainer); + Debug.Assert(containerField != null); + var fieldValue = containerField.GetValue(this._container); + var fieldList = fieldValue as IList; + + // Now get the referenced object from the parent container field (via ID as an index) + Debug.Assert(fieldList != null); + var referencingObject = fieldList[id]; + + // Now set our reference field + Debug.Assert(referencingObject != null); + this.SetProperty(name, referencingObject); + } + + private void ExtractReferencesProperties() + { + foreach (var (entryName, connection) in this.SectionProperties.ReferencesAttributes) + { + // this'll be a set of ids + var hasIDs = this.MaybeExtractCompoundInteger(entryName, out var ids); + if (hasIDs) + { + var (name, type) = connection; + this.ExtractReferencesProperty(ids, name, type); + } + } + } + + private void ExtractReferencesProperty(List ids, string name, System.Type type) + { + // The reference container in the parent class + //var referenceSectionProperties = GetSectionProperties(type); + //var referenceClassAttribute = referenceSectionProperties.ClassAttribute; + //Debug.Assert(referenceClassAttribute != null); + //var referencingContainer = referenceClassAttribute.Referencing; + + // Get the parent container field + //var containerType = this._container.GetType(); + //Debug.Assert(referencingContainer != null); + //var containerField = containerType.GetField(referencingContainer); + //Debug.Assert(containerField != null); + //var fieldValue = containerField.GetValue(this._container); + //var fieldList = fieldValue as IList; + + // Now get the referenced object from the parent container field (via ID as an index) + //Debug.Assert(fieldList != null); + //var referencingObject = fieldList[id]; + + //// Now set our reference field + //Debug.Assert(referencingObject != null); + //this.SetProperty(name, referencingObject); + } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Information.cs b/M6502/M6502.Symbols/Information.cs index 965be62..298e06e 100644 --- a/M6502/M6502.Symbols/Information.cs +++ b/M6502/M6502.Symbols/Information.cs @@ -1,45 +1,39 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + //info csym = 0, file = 3, lib = 0, line = 380, mod = 1, scope = 12, seg = 8, span = 356, sym = 61, type = 3 + [Section("info")] + public sealed class Information(Parser container) : Section(container) { - namespace Symbols - { - //info csym = 0, file = 3, lib = 0, line = 380, mod = 1, scope = 12, seg = 8, span = 356, sym = 61, type = 3 - [Section("info")] - public sealed class Information(Parser container) : Section(container) - { - [SectionProperty("csym")] - public int CSymbol { get; private set; } + [SectionProperty("csym")] + public int CSymbol { get; private set; } - [SectionProperty("file")] - public int File { get; private set; } + [SectionProperty("file")] + public int File { get; private set; } - [SectionProperty("lib")] - public int Library { get; private set; } + [SectionProperty("lib")] + public int Library { get; private set; } - [SectionProperty("line")] - public int Line { get; private set; } + [SectionProperty("line")] + public int Line { get; private set; } - [SectionProperty("mod")] - public int Module { get; private set; } + [SectionProperty("mod")] + public int Module { get; private set; } - [SectionProperty("scope")] - public int Scope { get; private set; } + [SectionProperty("scope")] + public int Scope { get; private set; } - [SectionProperty("seg")] - public int Segment { get; private set; } + [SectionProperty("seg")] + public int Segment { get; private set; } - [SectionProperty("span")] - public int Span { get; private set; } + [SectionProperty("span")] + public int Span { get; private set; } - [SectionProperty("sym")] - public int Symbol { get; private set; } + [SectionProperty("sym")] + public int Symbol { get; private set; } - [SectionProperty("type")] - public int Type { get; private set; } + [SectionProperty("type")] + public int Type { get; private set; } - public int Count(string key) => this.GetValueT(key); - } - } + public int Count(string key) => this.GetValueT(key); } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Line.cs b/M6502/M6502.Symbols/Line.cs index c012f1b..f8488d9 100644 --- a/M6502/M6502.Symbols/Line.cs +++ b/M6502/M6502.Symbols/Line.cs @@ -1,28 +1,22 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + // line id = 268, file = 1, line = 60, type = 2, count = 1, span = 286 + 195 + [Section("line", "Lines")] + public sealed class Line(Parser container) : IdentifiableSection(container) { - namespace Symbols - { - // line id = 268, file = 1, line = 60, type = 2, count = 1, span = 286 + 195 - [Section("line", "Lines")] - public sealed class Line(Parser container) : IdentifiableSection(container) - { - [SectionReference("file")] - public Symbols.File? File { get; private set; } + [SectionReference("file")] + public Symbols.File? File { get; private set; } - [SectionProperty("line")] - public int LineNumber { get; private set; } + [SectionProperty("line")] + public int LineNumber { get; private set; } - [SectionReference("type")] - public Symbols.Type? Type { get; private set; } + [SectionReference("type")] + public Symbols.Type? Type { get; private set; } - [SectionProperty("count")] - public int? Count { get; private set; } + [SectionProperty("count")] + public int? Count { get; private set; } - [SectionReferences("span")] - public List? Spans { get; private set; } - } - } + [SectionReferences("span")] + public List? Spans { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Module.cs b/M6502/M6502.Symbols/Module.cs index f3a3be2..1a7d58f 100644 --- a/M6502/M6502.Symbols/Module.cs +++ b/M6502/M6502.Symbols/Module.cs @@ -1,16 +1,10 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + // mod id=0,name="sudoku.o",file=0 + [Section("mod", "Modules")] + public sealed class Module(Parser container) : NamedSection(container) { - namespace Symbols - { - // mod id=0,name="sudoku.o",file=0 - [Section("mod", "Modules")] - public sealed class Module(Parser container) : NamedSection(container) - { - [SectionReference("file")] - public Symbols.File? File { get; private set; } - } - } + [SectionReference("file")] + public Symbols.File? File { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/NamedSection.cs b/M6502/M6502.Symbols/NamedSection.cs index 6dab51f..f5e9b2f 100644 --- a/M6502/M6502.Symbols/NamedSection.cs +++ b/M6502/M6502.Symbols/NamedSection.cs @@ -1,17 +1,11 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + public class NamedSection : IdentifiableSection { - namespace Symbols - { - public class NamedSection : IdentifiableSection - { - [SectionProperty("name")] - public string? Name { get; protected set; } + [SectionProperty("name")] + public string? Name { get; protected set; } - protected NamedSection(Parser container) - : base(container) { } - } - } + protected NamedSection(Parser container) + : base(container) { } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Parser.cs b/M6502/M6502.Symbols/Parser.cs index d7c22d1..fe1923b 100644 --- a/M6502/M6502.Symbols/Parser.cs +++ b/M6502/M6502.Symbols/Parser.cs @@ -1,428 +1,422 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + using System; + using System.Diagnostics; + using System.Threading.Tasks; + + public sealed class Parser { - namespace Symbols + #region Variables, properties etc. + + private Version? _version; + private Information? _information; + + // Section -> Unique ID list of dictionary entries + public Dictionary>> Parsed { get; } = []; + + public List
SectionEntries { get; } = []; + + public List Files = []; + + public List Lines = []; + + public List Modules = []; + + public List Segments = []; + + public List Spans = []; + + public List Scopes = []; + + public List Symbols = []; + + public List Types = []; + + + // Symbol sub-types + public IEnumerable Labels => this.SelectSymbolsByType("lab"); + public IEnumerable Equates => this.SelectSymbolsByType("equ"); + + // Scope clarification + public List AddressableScopes = []; + + // Scope cache for precomputed ranges + private readonly int?[] _scopeAddressCache = new int?[0x10000]; + + #endregion + + #region Lookups + + private static IEnumerable SelectIdMatching(int id, IEnumerable? items) where T : IdentifiableSection => from item in items where item.ID == id select item; + + private IEnumerable SelectSymbolsByType(string type) => from symbol in this.Symbols where symbol.Type == type select symbol; + + #region Label lookup + + private IEnumerable LookupLabelsByAddress(int address) => from label in this.Labels where label.Value == address select label; + + public Symbol? LookupLabelByAddress(int address) { - using System; - using System.Diagnostics; - using System.Threading.Tasks; + var labels = this.LookupLabelsByAddress(address).ToList(); + return labels.Count > 0 ? labels[0] : null; + } - public sealed class Parser + private IEnumerable LookupLabelsByID(int id) => SelectIdMatching(id, this.Labels); + + public Symbol? LookupLabelByID(int id) + { + var labels = this.LookupLabelsByID(id).ToList(); + return labels.Count > 0 ? labels[0] : null; + } + + #endregion + + #region Constant lookup + + private IEnumerable LookupEquatesByValue(int constant) => from equate in this.Equates where equate.Value == constant select equate; + + public Symbol? LookupEquateByValue(int constant) + { + var equates = this.LookupEquatesByValue(constant).ToList(); + return equates.Count > 0 ? equates[0] : null; + } + + #endregion + + #region Scope lookup + + private int LocateScopeByAddress(int address) + { + var low = 0; + var high = this.AddressableScopes.Count - 1; + + while (low <= high) { - #region Variables, properties etc. + var mid = low + ((high - low) / 2); - private Version? _version; - private Information? _information; + var scope = this.AddressableScopes[mid]; - // Section -> Unique ID list of dictionary entries - public Dictionary>> Parsed { get; } = []; + var symbol = scope.Symbol; + Debug.Assert(symbol != null); + var start = symbol.Value; - public List
SectionEntries { get; } = []; - - public List Files = []; - - public List Lines = []; - - public List Modules = []; - - public List Segments = []; - - public List Spans = []; - - public List Scopes = []; - - public List Symbols = []; - - public List Types = []; - - - // Symbol sub-types - public IEnumerable Labels => this.SelectSymbolsByType("lab"); - public IEnumerable Equates => this.SelectSymbolsByType("equ"); - - // Scope clarification - public List AddressableScopes = []; - - // Scope cache for precomputed ranges - private readonly int?[] _scopeAddressCache = new int?[0x10000]; - - #endregion - - #region Lookups - - private static IEnumerable SelectIdMatching(int id, IEnumerable? items) where T : IdentifiableSection => from item in items where item.ID == id select item; - - private IEnumerable SelectSymbolsByType(string type) => from symbol in this.Symbols where symbol.Type == type select symbol; - - #region Label lookup - - private IEnumerable LookupLabelsByAddress(int address) => from label in this.Labels where label.Value == address select label; - - public Symbol? LookupLabelByAddress(int address) + if (address < start) { - var labels = this.LookupLabelsByAddress(address).ToList(); - return labels.Count > 0 ? labels[0] : null; + high = mid - 1; } - - private IEnumerable LookupLabelsByID(int id) => SelectIdMatching(id, this.Labels); - - public Symbol? LookupLabelByID(int id) + else { - var labels = this.LookupLabelsByID(id).ToList(); - return labels.Count > 0 ? labels[0] : null; - } - - #endregion - - #region Constant lookup - - private IEnumerable LookupEquatesByValue(int constant) => from equate in this.Equates where equate.Value == constant select equate; - - public Symbol? LookupEquateByValue(int constant) - { - var equates = this.LookupEquatesByValue(constant).ToList(); - return equates.Count > 0 ? equates[0] : null; - } - - #endregion - - #region Scope lookup - - private int LocateScopeByAddress(int address) - { - var low = 0; - var high = this.AddressableScopes.Count - 1; - - while (low <= high) + var end = start + scope.Size; + if (address >= end) { - var mid = low + ((high - low) / 2); - - var scope = this.AddressableScopes[mid]; - - var symbol = scope.Symbol; - Debug.Assert(symbol != null); - var start = symbol.Value; - - if (address < start) - { - high = mid - 1; - } - else - { - var end = start + scope.Size; - if (address >= end) - { - low = mid + 1; - } - else - { - return mid; - } - } - } - - // If we reach here, then scope was not present - return -1; - } - - public Scope? LookupScopeByAddress(int address) - { - var index = _scopeAddressCache[address] ?? (_scopeAddressCache[address] = this.LocateScopeByAddress(address)); - return index == -1 ? null : this.AddressableScopes[index.Value]; - } - - private IEnumerable LookupScopesByID(int id) => SelectIdMatching(id, this.Scopes); - - public Scope? LookupScopeByID(int id) - { - var scopes = this.LookupScopesByID(id).ToList(); - return scopes.Count > 0 ? scopes[0] : null; - } - - #endregion - - #region Scope evaluation - - private static List EvaluateScope(Scope? start) - { - Debug.Assert(start != null); - var returned = new List(); - for (var current = start; current.Parent != null; current = current.Parent) - { - returned.Add(current); - } - return returned; - } - - private static List EvaluateScope(Symbol symbol) => EvaluateScope(symbol.Scope); - - #endregion - - #region Namespace evaluation from scope - - private 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(!string.IsNullOrEmpty(name)); - returned += name; - var last = i == 0; - if (!last) - { - returned += '.'; - } - } - return returned; - } - - private static string PrefixNamespace(Symbol? symbol) - { - if (symbol is null) - { - return string.Empty; - } - var prefix = BuildNamespace(symbol); - var name = symbol.Name; - Debug.Assert(!string.IsNullOrEmpty(name)); - return string.IsNullOrEmpty(prefix) ? name : $"{prefix}.{name}"; - } - - #endregion - - #region Qualified symbol lookup - - public bool TryGetQualifiedLabelByAddress(ushort absolute, out string label) - { - var symbol = this.LookupLabelByAddress(absolute); - label = PrefixNamespace(symbol); - return symbol != null; - } - - public string MaybeGetQualifiedLabelByAddress(ushort absolute) => this.TryGetQualifiedLabelByAddress(absolute, out var label) ? label : string.Empty; - - public bool TryGetQualifiedEquateValue(ushort value, out string name) - { - var symbol = this.LookupEquateByValue(value); - name = PrefixNamespace(symbol); - return symbol != null; - } - - #endregion - - #endregion - - #region Section extractors - - private void ExtractSections() - { - this.ExtractFiles(); - this.ExtractLines(); - this.ExtractModules(); - this.ExtractSegments(); - this.ExtractSpans(); - this.ExtractScopes(); - this.ExtractSymbols(); - this.ExtractTypes(); - } - - 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 - { - if (!this.Parsed.TryGetValue(key, out var parsed)) - { - throw new ArgumentOutOfRangeException(nameof(key), key, "Debugging section is unavailable"); - } - this.ExtractIdentifiableSection(key, into, parsed); - } - - private void ExtractIdentifiableSection(string key, List into, List> parsed) where T : IdentifiableSection - { - into.Capacity = parsed.Count; - for (var id = 0; id < parsed.Count; ++id) - { - this.ExtractIdentifiableEntry(id, parsed, into); - } - Debug.Assert(into.Count == parsed.Count); - this.VerifyInformationCount(key, into.Count); - } - - private void ExtractIdentifiableEntry(int id, List> parsed, List into) where T : IdentifiableSection - { - Debug.Assert(into.Count == id); - var information = parsed[id]; - var entry = this.ExtractEntry(information); - Debug.Assert(into.Capacity > id); - into.Add(entry); - } - - private T ExtractEntry(Dictionary information) where T : Section - { - var entry = (T?)Activator.CreateInstance(typeof(T), this); - Debug.Assert(entry != null); - entry.Parse(information); - return entry; - } - - private void VerifyInformationCount(string key, int actual) - { - Debug.Assert(this._information != null); - var expected = this._information?.Count(key); - if (expected != actual) - { - throw new InvalidOperationException($"information count mismatch for {key}. Expected {expected}, actual {actual}"); - } - } - - #endregion - - private void ExtractReferences() - { - foreach (var entry in this.SectionEntries) - { - var identifiableEntry = entry as IdentifiableSection; - identifiableEntry?.ExtractReferences(); - } - } - - private void BuildAddressableScopes() - { - var scopes = from scope in this.Scopes where scope.Symbol != null select scope; - this.AddressableScopes.AddRange(scopes); - } - - #region Parser driver - - public async Task ParseAsync(string? path) - { - if (string.IsNullOrEmpty(path)) - { - return; - } - - using var reader = new StreamReader(path); - await this.ParseAsync(reader); - - this.ExtractSections(); - this.ExtractReferences(); - - this.Parsed.Clear(); - - this.BuildAddressableScopes(); - } - - private async Task ParseAsync(StreamReader reader) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync(); - if (line == null) - { - break; - } - - this.ParseLine(line.Split(' ', '\t')); - } - } - - private void ParseLine(string[] elements) - { - ArgumentNullException.ThrowIfNull(elements, nameof(elements)); - ArgumentOutOfRangeException.ThrowIfNotEqual(elements.Length, 2, nameof(elements)); - - var key = elements[0]; - var parts = elements[1].Split(','); - - if (key is "version") - { - if (this._version != null) - { - throw new InvalidOperationException("Verson object has already been parsed"); - } - this._version = new(this); - this._version.Parse(BuildDictionary(parts)); - this.VerifyVersion(); - } - else if (key is "info") - { - if (this._information != null) - { - throw new InvalidOperationException("Information object has already been parsed"); - } - this._information = new(this); - this._information.Parse(BuildDictionary(parts)); + low = mid + 1; } else { - this.Parse(key, parts); + return mid; } } + } - private void Parse(string key, string[] parts) + // If we reach here, then scope was not present + return -1; + } + + public Scope? LookupScopeByAddress(int address) + { + var index = _scopeAddressCache[address] ?? (_scopeAddressCache[address] = this.LocateScopeByAddress(address)); + return index == -1 ? null : this.AddressableScopes[index.Value]; + } + + private IEnumerable LookupScopesByID(int id) => SelectIdMatching(id, this.Scopes); + + public Scope? LookupScopeByID(int id) + { + var scopes = this.LookupScopesByID(id).ToList(); + return scopes.Count > 0 ? scopes[0] : null; + } + + #endregion + + #region Scope evaluation + + private static List EvaluateScope(Scope? start) + { + Debug.Assert(start != null); + var returned = new List(); + for (var current = start; current.Parent != null; current = current.Parent) + { + returned.Add(current); + } + return returned; + } + + private static List EvaluateScope(Symbol symbol) => EvaluateScope(symbol.Scope); + + #endregion + + #region Namespace evaluation from scope + + private 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(!string.IsNullOrEmpty(name)); + returned += name; + var last = i == 0; + if (!last) { - if (!this.Parsed.TryGetValue(key, out var section)) - { - this.Parsed.Add(key, section = []); - } - section.Add(BuildDictionary(parts)); + returned += '.'; } + } + return returned; + } - private static Dictionary BuildDictionary(string[] parts) - { - var dictionary = new Dictionary(parts.Length); - foreach (var part in parts) - { - var definition = part.Split('='); - if (definition.Length != 2) - { - throw new InvalidOperationException("Invalid symbol file format (definition is missing equals)"); - } + private static string PrefixNamespace(Symbol? symbol) + { + if (symbol is null) + { + return string.Empty; + } + var prefix = BuildNamespace(symbol); + var name = symbol.Name; + Debug.Assert(!string.IsNullOrEmpty(name)); + return string.IsNullOrEmpty(prefix) ? name : $"{prefix}.{name}"; + } - var key = definition[0]; - var value = definition[1]; - dictionary.Add(key, value); - } + #endregion - return dictionary; - } + #region Qualified symbol lookup - private void VerifyVersion() - { - if (this._version == null) - { - throw new InvalidOperationException("Version has not yet been parsed"); - } + public bool TryGetQualifiedLabelByAddress(ushort absolute, out string label) + { + var symbol = this.LookupLabelByAddress(absolute); + label = PrefixNamespace(symbol); + return symbol != null; + } - var major = this._version?.Major; - var minor = this._version?.Minor; - var valid = major == 2 && minor == 0; - if (!valid) - { - throw new InvalidOperationException($"Unknown symbol file version: {major}.{minor}"); - } - } + public string MaybeGetQualifiedLabelByAddress(ushort absolute) => this.TryGetQualifiedLabelByAddress(absolute, out var label) ? label : string.Empty; - #endregion + public bool TryGetQualifiedEquateValue(ushort value, out string name) + { + var symbol = this.LookupEquateByValue(value); + name = PrefixNamespace(symbol); + return symbol != null; + } + + #endregion + + #endregion + + #region Section extractors + + private void ExtractSections() + { + this.ExtractFiles(); + this.ExtractLines(); + this.ExtractModules(); + this.ExtractSegments(); + this.ExtractSpans(); + this.ExtractScopes(); + this.ExtractSymbols(); + this.ExtractTypes(); + } + + 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 + { + if (!this.Parsed.TryGetValue(key, out var parsed)) + { + throw new ArgumentOutOfRangeException(nameof(key), key, "Debugging section is unavailable"); + } + this.ExtractIdentifiableSection(key, into, parsed); + } + + private void ExtractIdentifiableSection(string key, List into, List> parsed) where T : IdentifiableSection + { + into.Capacity = parsed.Count; + for (var id = 0; id < parsed.Count; ++id) + { + this.ExtractIdentifiableEntry(id, parsed, into); + } + Debug.Assert(into.Count == parsed.Count); + this.VerifyInformationCount(key, into.Count); + } + + private void ExtractIdentifiableEntry(int id, List> parsed, List into) where T : IdentifiableSection + { + Debug.Assert(into.Count == id); + var information = parsed[id]; + var entry = this.ExtractEntry(information); + Debug.Assert(into.Capacity > id); + into.Add(entry); + } + + private T ExtractEntry(Dictionary information) where T : Section + { + var entry = (T?)Activator.CreateInstance(typeof(T), this); + Debug.Assert(entry != null); + entry.Parse(information); + return entry; + } + + private void VerifyInformationCount(string key, int actual) + { + Debug.Assert(this._information != null); + var expected = this._information?.Count(key); + if (expected != actual) + { + throw new InvalidOperationException($"information count mismatch for {key}. Expected {expected}, actual {actual}"); } } + + #endregion + + private void ExtractReferences() + { + foreach (var entry in this.SectionEntries) + { + var identifiableEntry = entry as IdentifiableSection; + identifiableEntry?.ExtractReferences(); + } + } + + private void BuildAddressableScopes() + { + var scopes = from scope in this.Scopes where scope.Symbol != null select scope; + this.AddressableScopes.AddRange(scopes); + } + + #region Parser driver + + public async Task ParseAsync(string? path) + { + if (string.IsNullOrEmpty(path)) + { + return; + } + + using var reader = new StreamReader(path); + await this.ParseAsync(reader); + + this.ExtractSections(); + this.ExtractReferences(); + + this.Parsed.Clear(); + + this.BuildAddressableScopes(); + } + + private async Task ParseAsync(StreamReader reader) + { + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync(); + if (line == null) + { + break; + } + + this.ParseLine(line.Split(' ', '\t')); + } + } + + private void ParseLine(string[] elements) + { + ArgumentNullException.ThrowIfNull(elements, nameof(elements)); + ArgumentOutOfRangeException.ThrowIfNotEqual(elements.Length, 2, nameof(elements)); + + var key = elements[0]; + var parts = elements[1].Split(','); + + if (key is "version") + { + if (this._version != null) + { + throw new InvalidOperationException("Verson object has already been parsed"); + } + this._version = new(this); + this._version.Parse(BuildDictionary(parts)); + this.VerifyVersion(); + } + else if (key is "info") + { + if (this._information != null) + { + throw new InvalidOperationException("Information object has already been parsed"); + } + this._information = new(this); + this._information.Parse(BuildDictionary(parts)); + } + else + { + this.Parse(key, parts); + } + } + + private void Parse(string key, string[] parts) + { + if (!this.Parsed.TryGetValue(key, out var section)) + { + this.Parsed.Add(key, section = []); + } + section.Add(BuildDictionary(parts)); + } + + private static Dictionary BuildDictionary(string[] parts) + { + var dictionary = new Dictionary(parts.Length); + 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.Add(key, value); + } + + return dictionary; + } + + private void VerifyVersion() + { + if (this._version == null) + { + throw new InvalidOperationException("Version has not yet been parsed"); + } + + var major = this._version?.Major; + var minor = this._version?.Minor; + var valid = major == 2 && minor == 0; + if (!valid) + { + throw new InvalidOperationException($"Unknown symbol file version: {major}.{minor}"); + } + } + + #endregion } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/ReflectedSectionProperties.cs b/M6502/M6502.Symbols/ReflectedSectionProperties.cs index 60bfa44..95bfb31 100644 --- a/M6502/M6502.Symbols/ReflectedSectionProperties.cs +++ b/M6502/M6502.Symbols/ReflectedSectionProperties.cs @@ -1,226 +1,220 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + using System.Diagnostics; + using System.Reflection; + + public sealed class ReflectedSectionProperties { - namespace Symbols + private readonly Dictionary _entriesToProperties = []; + + public HashSet StringKeys { get; } = []; + public HashSet EnumerationKeys { get; } = []; + public HashSet IntegerKeys { get; } = []; + public HashSet HexIntegerKeys { get; } = []; + public HashSet LongKeys { get; } = []; + public HashSet HexLongKeys { get; } = []; + public HashSet DateTimeKeys { get; } = []; + public HashSet MultipleKeys { get; } = []; + + public Dictionary Properties { get; } = []; + + internal SectionAttribute? ClassAttribute { get; private set; } + + internal Dictionary> ReferenceAttributes { get; } = []; + + internal Dictionary> ReferencesAttributes { get; } = []; + + public void Build(System.Type type) { - using System.Diagnostics; - using System.Reflection; + var sectionAttributes = type.GetCustomAttributes(typeof(SectionAttribute), true); + Debug.Assert(sectionAttributes != null, "No section attributes available"); + Debug.Assert(sectionAttributes.Length == 1, "Must be a single section attribute available"); + var sectionAttribute = sectionAttributes[0]; + Debug.Assert(sectionAttribute != null, "Section attribute cannot be null"); + this.ClassAttribute = (SectionAttribute)sectionAttribute; - public sealed class ReflectedSectionProperties + foreach (var property in type.GetProperties()) { - private readonly Dictionary _entriesToProperties = []; + this.ProcessPropertyAttributes(property); + } + } - public HashSet StringKeys { get; } = []; - public HashSet EnumerationKeys { get; } = []; - public HashSet IntegerKeys { get; } = []; - public HashSet HexIntegerKeys { get; } = []; - public HashSet LongKeys { get; } = []; - public HashSet HexLongKeys { get; } = []; - public HashSet DateTimeKeys { get; } = []; - public HashSet MultipleKeys { get; } = []; + public string GetPropertyNameFromEntryName(string name) + { + var found = this._entriesToProperties.TryGetValue(name, out var propertyName); + if (!found) + { + throw new ArgumentOutOfRangeException(nameof(name), name, "Missing property mapping"); + } + Debug.Assert(propertyName != null); + return propertyName; + } - public Dictionary Properties { get; } = []; + private PropertyInfo GetPropertyFromEntryName(string key) + { + var propertyName = this.GetPropertyNameFromEntryName(key); + var propertyAvailable = this.Properties.TryGetValue(propertyName, out var property); + Debug.Assert(propertyAvailable); + Debug.Assert(property != null); + return property; + } - internal SectionAttribute? ClassAttribute { get; private set; } + public object? GetValue(object? obj, string key) + { + var property = this.GetPropertyFromEntryName(key); + Debug.Assert(property.CanRead); + return property.GetValue(obj); + } - internal Dictionary> ReferenceAttributes { get; } = []; + public void SetValue(object? obj, string key, object? value) + { + var property = this.GetPropertyFromEntryName(key); + Debug.Assert(property.CanWrite); + property.SetValue(obj, value); ; + } - internal Dictionary> ReferencesAttributes { get; } = []; + public T? MaybeGetValue(object? obj, string key) => (T?)this.GetValue(obj, key); - public void Build(System.Type type) + public T GetValueT(object? obj, string key) + { + var possible = this.MaybeGetValue(obj, key); + return possible != null ? possible : throw new ArgumentOutOfRangeException(nameof(key), key, "Property read issue"); + } + + private void ProcessPropertyAttributes(PropertyInfo property) + { + var attributes = property.GetCustomAttributes(typeof(SectionPropertyAttribute), true); + if (attributes.Length > 0) + { + Debug.Assert(attributes.Length == 1, "Too many section property attributes"); + this.Properties.Add(property.Name, property); + this.ProcessSectionPropertyAttribute(property.PropertyType, property.Name, attributes[0]); + } + } + + private void ProcessSectionPropertyAttribute(System.Type? type, string name, object attribute) + { + ArgumentNullException.ThrowIfNull(type, nameof(type)); + this.ProcessSectionPropertyAttribute(type, name, (SectionPropertyAttribute)attribute); + } + + public void AddStringKey(string key) + { + var added = this.StringKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddEnumerationKey(string key) + { + var added = this.EnumerationKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddMultiplesKey(string key) + { + var added = this.MultipleKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddHexIntegerKey(string key) + { + var added = this.HexIntegerKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddIntegerKey(string key) + { + var added = this.IntegerKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddHexLongKey(string key) + { + var added = this.HexLongKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddLongKey(string key) + { + var added = this.LongKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + public void AddDateTimeKey(string key) + { + var added = this.DateTimeKeys.Add(key); + Debug.Assert(added, $"<{key}> already has an entry"); + } + + private void ProcessSectionPropertyAttribute(System.Type originalType, string name, SectionPropertyAttribute attribute) + { + var key = attribute.Key; + this._entriesToProperties.Add(key, name); + + if (attribute is SectionReferenceAttribute) + { + this.ReferenceAttributes.Add(key, new Tuple(name, originalType)); + } else if (attribute is SectionReferencesAttribute referencesAttribute) + { + this.ReferencesAttributes.Add(key, new Tuple(name, originalType)); + } + + var multiples = attribute.Many; + if (multiples) + { + // Type is irrelevant + this.AddMultiplesKey(key); + return; + } + + var type = attribute.Type ?? originalType; + + var enumeration = attribute.Enumeration; + if (enumeration) + { + Debug.Assert(type == typeof(string), "Enumeration must be of type string"); + this.AddEnumerationKey(key); + return; + } + + var hex = attribute.Hexadecimal; + + if (type == typeof(string)) + { + Debug.Assert(!enumeration, "Enumeration case should already have been handled"); + Debug.Assert(!hex, "Cannot have a hexadecimal string type"); + this.AddStringKey(key); + } + else if (type == typeof(int) || type == typeof(Nullable)) + { + if (hex) { - var sectionAttributes = type.GetCustomAttributes(typeof(SectionAttribute), true); - Debug.Assert(sectionAttributes != null, "No section attributes available"); - Debug.Assert(sectionAttributes.Length == 1, "Must be a single section attribute available"); - var sectionAttribute = sectionAttributes[0]; - Debug.Assert(sectionAttribute != null, "Section attribute cannot be null"); - this.ClassAttribute = (SectionAttribute)sectionAttribute; - - foreach (var property in type.GetProperties()) - { - this.ProcessPropertyAttributes(property); - } + this.AddHexIntegerKey(key); } - - public string GetPropertyNameFromEntryName(string name) + else { - var found = this._entriesToProperties.TryGetValue(name, out var propertyName); - if (!found) - { - throw new ArgumentOutOfRangeException(nameof(name), name, "Missing property mapping"); - } - Debug.Assert(propertyName != null); - return propertyName; + this.AddIntegerKey(key); } - - private PropertyInfo GetPropertyFromEntryName(string key) + } + else if (type == typeof(long) || type == typeof(Nullable)) + { + if (hex) { - var propertyName = this.GetPropertyNameFromEntryName(key); - var propertyAvailable = this.Properties.TryGetValue(propertyName, out var property); - Debug.Assert(propertyAvailable); - Debug.Assert(property != null); - return property; + this.AddHexLongKey(key); } - - public object? GetValue(object? obj, string key) + else { - var property = this.GetPropertyFromEntryName(key); - Debug.Assert(property.CanRead); - return property.GetValue(obj); - } - - public void SetValue(object? obj, string key, object? value) - { - var property = this.GetPropertyFromEntryName(key); - Debug.Assert(property.CanWrite); - property.SetValue(obj, value); ; - } - - public T? MaybeGetValue(object? obj, string key) => (T?)this.GetValue(obj, key); - - public T GetValueT(object? obj, string key) - { - var possible = this.MaybeGetValue(obj, key); - return possible != null ? possible : throw new ArgumentOutOfRangeException(nameof(key), key, "Property read issue"); - } - - private void ProcessPropertyAttributes(PropertyInfo property) - { - var attributes = property.GetCustomAttributes(typeof(SectionPropertyAttribute), true); - if (attributes.Length > 0) - { - Debug.Assert(attributes.Length == 1, "Too many section property attributes"); - this.Properties.Add(property.Name, property); - this.ProcessSectionPropertyAttribute(property.PropertyType, property.Name, attributes[0]); - } - } - - private void ProcessSectionPropertyAttribute(System.Type? type, string name, object attribute) - { - ArgumentNullException.ThrowIfNull(type, nameof(type)); - this.ProcessSectionPropertyAttribute(type, name, (SectionPropertyAttribute)attribute); - } - - public void AddStringKey(string key) - { - var added = this.StringKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddEnumerationKey(string key) - { - var added = this.EnumerationKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddMultiplesKey(string key) - { - var added = this.MultipleKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddHexIntegerKey(string key) - { - var added = this.HexIntegerKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddIntegerKey(string key) - { - var added = this.IntegerKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddHexLongKey(string key) - { - var added = this.HexLongKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddLongKey(string key) - { - var added = this.LongKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - public void AddDateTimeKey(string key) - { - var added = this.DateTimeKeys.Add(key); - Debug.Assert(added, $"<{key}> already has an entry"); - } - - private void ProcessSectionPropertyAttribute(System.Type originalType, string name, SectionPropertyAttribute attribute) - { - var key = attribute.Key; - this._entriesToProperties.Add(key, name); - - if (attribute is SectionReferenceAttribute) - { - this.ReferenceAttributes.Add(key, new Tuple(name, originalType)); - } else if (attribute is SectionReferencesAttribute referencesAttribute) - { - this.ReferencesAttributes.Add(key, new Tuple(name, originalType)); - } - - var multiples = attribute.Many; - if (multiples) - { - // Type is irrelevant - this.AddMultiplesKey(key); - return; - } - - var type = attribute.Type ?? originalType; - - var enumeration = attribute.Enumeration; - if (enumeration) - { - Debug.Assert(type == typeof(string), "Enumeration must be of type string"); - this.AddEnumerationKey(key); - return; - } - - var hex = attribute.Hexadecimal; - - if (type == typeof(string)) - { - Debug.Assert(!enumeration, "Enumeration case should already have been handled"); - Debug.Assert(!hex, "Cannot have a hexadecimal string type"); - this.AddStringKey(key); - } - else if (type == typeof(int) || type == typeof(Nullable)) - { - if (hex) - { - this.AddHexIntegerKey(key); - } - else - { - this.AddIntegerKey(key); - } - } - else if (type == typeof(long) || type == typeof(Nullable)) - { - if (hex) - { - this.AddHexLongKey(key); - } - else - { - this.AddLongKey(key); - } - } - else if (type == typeof(DateTime) || type == typeof(Nullable)) - { - this.AddDateTimeKey(key); - } - else - { - throw new NotImplementedException($"Property type <{type}> has not been implemented"); - } + this.AddLongKey(key); } } + else if (type == typeof(DateTime) || type == typeof(Nullable)) + { + this.AddDateTimeKey(key); + } + else + { + throw new NotImplementedException($"Property type <{type}> has not been implemented"); + } } } } diff --git a/M6502/M6502.Symbols/Scope.cs b/M6502/M6502.Symbols/Scope.cs index 285934f..24c5160 100644 --- a/M6502/M6502.Symbols/Scope.cs +++ b/M6502/M6502.Symbols/Scope.cs @@ -1,33 +1,27 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + //scope id = 0, name = "", mod = 0, size = 1137, span = 355 + 354 + //scope id = 1, name = "stack", mod = 0, type = scope, size = 7, parent = 0, span = 15 + //scope id = 7, name = "print_box_break_vertical", mod = 0, type = scope, size = 6, parent = 0, sym = 33, span = 72 + [Section("scope", "Scopes")] + public sealed class Scope(Parser container) : NamedSection(container) { - namespace Symbols - { - //scope id = 0, name = "", mod = 0, size = 1137, span = 355 + 354 - //scope id = 1, name = "stack", mod = 0, type = scope, size = 7, parent = 0, span = 15 - //scope id = 7, name = "print_box_break_vertical", mod = 0, type = scope, size = 6, parent = 0, sym = 33, span = 72 - [Section("scope", "Scopes")] - public sealed class Scope(Parser container) : NamedSection(container) - { - [SectionReference("mod")] - public Symbols.Module? Module { get; private set; } + [SectionReference("mod")] + public Symbols.Module? Module { get; private set; } - [SectionProperty("type")] - public string? Type { get; private set; } + [SectionProperty("type")] + public string? Type { get; private set; } - [SectionProperty("size")] - public int Size { get; private set; } + [SectionProperty("size")] + public int Size { get; private set; } - [SectionReference("parent", optional: true)] - public Scope? Parent { get; private set; } + [SectionReference("parent", optional: true)] + public Scope? Parent { get; private set; } - [SectionReference("sym", optional: true)] - public Symbols.Symbol? Symbol { get; private set; } + [SectionReference("sym", optional: true)] + public Symbols.Symbol? Symbol { get; private set; } - [SectionReferences("span")] - public List? Spans { get; private set; } - } - } + [SectionReferences("span")] + public List? Spans { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Section.cs b/M6502/M6502.Symbols/Section.cs index ff2ba42..452f2e6 100644 --- a/M6502/M6502.Symbols/Section.cs +++ b/M6502/M6502.Symbols/Section.cs @@ -1,162 +1,156 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + using System; + using System.Diagnostics; + using System.Globalization; + using System.Numerics; + using System.Reflection; + + public class Section { - namespace Symbols + protected static readonly Dictionary _sectionPropertiesCache = []; + protected static readonly DateTime _unixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + protected readonly Parser _container; + + // Needed to evaluate references on a second pass + protected readonly Dictionary _references = []; + protected readonly Dictionary> _multipleReferences = []; + + protected Dictionary? _parsed; + + protected ReflectedSectionProperties SectionProperties => GetSectionProperties(this.GetType()); + + protected static ReflectedSectionProperties GetSectionProperties(System.Type type) { - using System; - using System.Diagnostics; - using System.Globalization; - using System.Numerics; - using System.Reflection; + var obtained = _sectionPropertiesCache.TryGetValue(type, out var properties); + Debug.Assert(obtained, $"Section properties for {type.Name} have not been built"); + Debug.Assert(properties != null); + return properties; + } - public class Section + protected Section(Parser container) => this._container = container; + + protected void ProcessAttributesOfProperties() + { + var type = this.GetType(); + var entry = new ReflectedSectionProperties(); + Debug.Assert(_sectionPropertiesCache != null); + if (_sectionPropertiesCache.TryAdd(type, entry)) { - protected static readonly Dictionary _sectionPropertiesCache = []; - protected static readonly DateTime _unixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - protected readonly Parser _container; - - // Needed to evaluate references on a second pass - protected readonly Dictionary _references = []; - protected readonly Dictionary> _multipleReferences = []; - - protected Dictionary? _parsed; - - protected ReflectedSectionProperties SectionProperties => GetSectionProperties(this.GetType()); - - protected static ReflectedSectionProperties GetSectionProperties(System.Type type) - { - var obtained = _sectionPropertiesCache.TryGetValue(type, out var properties); - Debug.Assert(obtained, $"Section properties for {type.Name} have not been built"); - Debug.Assert(properties != null); - return properties; - } - - protected Section(Parser container) => this._container = container; - - protected void ProcessAttributesOfProperties() - { - var type = this.GetType(); - var entry = new ReflectedSectionProperties(); - Debug.Assert(_sectionPropertiesCache != null); - if (_sectionPropertiesCache.TryAdd(type, entry)) - { - entry.Build(type); - } - } - - public T GetValueT(string key) => this.SectionProperties.GetValueT(this, key); - - public virtual void Parse(Dictionary entries) - { - this._parsed = entries; - this._container.SectionEntries.Add(this); - this.ProcessAttributesOfProperties(); - foreach (var entry in entries) - { - var (key, value) = entry; - this.Parse(key, value); - } - } - - private void Parse(string key, string value) - { - var propertyName = this.SectionProperties.GetPropertyNameFromEntryName(key); - var propertyAvailable = this.SectionProperties.Properties.TryGetValue(propertyName, out var property); - if (!propertyAvailable) - { - throw new InvalidOperationException($"Unable to locate property name {propertyName} using reflection"); - } - Debug.Assert(property != null); - this.Parse(property, key, value); - } - - private void Parse(PropertyInfo property, string key, string value) - { - var reference = this.SectionProperties.ReferenceAttributes.ContainsKey(key); - var references = this.SectionProperties.ReferencesAttributes.ContainsKey(key); - var lazy = reference || references; - - if (lazy) - { - if (reference) - { - this._references.Add(key, ExtractInteger(value)); - } - else if (references) - { - this._multipleReferences.Add(key, ExtractCompoundInteger(value)); - } - else - { - throw new InvalidOperationException($"Getting here should be impossible! Key {key} is lazy, but not a reference"); - } - return; - } - - if (this.SectionProperties.StringKeys.Contains(key)) - { - property?.SetValue(this, ExtractString(value)); - } - else if (this.SectionProperties.EnumerationKeys.Contains(key)) - { - property?.SetValue(this, ExtractEnumeration(value)); - } - else if (this.SectionProperties.IntegerKeys.Contains(key)) - { - property?.SetValue(this, ExtractInteger(value)); - } - else if (this.SectionProperties.HexIntegerKeys.Contains(key)) - { - property?.SetValue(this, ExtractHexInteger(value)); - } - else if (this.SectionProperties.LongKeys.Contains(key)) - { - property?.SetValue(this, ExtractLong(value)); - } - else if (this.SectionProperties.HexLongKeys.Contains(key)) - { - property?.SetValue(this, ExtractHexLong(value)); - } - else if (this.SectionProperties.DateTimeKeys.Contains(key)) - { - property?.SetValue(this, ExtractDateTime(value)); - } - else if (this.SectionProperties.MultipleKeys.Contains(key)) - { - property?.SetValue(this, ExtractCompoundInteger(value)); - } - else - { - throw new InvalidOperationException($"Section: {key} has not been categorised"); - } - } - - protected static string ExtractString(string value) => value.Trim('"'); - protected static string ExtractEnumeration(string value) => value; - - private static T ExtractHexValue(string value) where T : INumberBase => T.Parse(value.AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); - private static T ExtractNumericValue(string value) where T : IParsable => T.Parse(value, CultureInfo.InvariantCulture); - - protected static int ExtractHexInteger(string value) => ExtractHexValue(value); - protected static long ExtractHexLong(string value) => ExtractHexValue(value); - protected static int ExtractInteger(string value) => ExtractNumericValue(value); - protected static long ExtractLong(string value) => ExtractNumericValue(value); - protected static DateTime ExtractDateTime(string value) => _unixEpoch.AddSeconds(ExtractHexLong(value)); - protected static string[] ExtractCompoundString(string value) => value.Split('+'); - - protected static List ExtractCompoundInteger(string value) - { - var elements = ExtractCompoundString(value); - var returned = new List(elements.Length); - foreach (var element in elements) - { - returned.Add(ExtractInteger(element)); - } - return returned; - } + entry.Build(type); } } + + public T GetValueT(string key) => this.SectionProperties.GetValueT(this, key); + + public virtual void Parse(Dictionary entries) + { + this._parsed = entries; + this._container.SectionEntries.Add(this); + this.ProcessAttributesOfProperties(); + foreach (var entry in entries) + { + var (key, value) = entry; + this.Parse(key, value); + } + } + + private void Parse(string key, string value) + { + var propertyName = this.SectionProperties.GetPropertyNameFromEntryName(key); + var propertyAvailable = this.SectionProperties.Properties.TryGetValue(propertyName, out var property); + if (!propertyAvailable) + { + throw new InvalidOperationException($"Unable to locate property name {propertyName} using reflection"); + } + Debug.Assert(property != null); + this.Parse(property, key, value); + } + + private void Parse(PropertyInfo property, string key, string value) + { + var reference = this.SectionProperties.ReferenceAttributes.ContainsKey(key); + var references = this.SectionProperties.ReferencesAttributes.ContainsKey(key); + var lazy = reference || references; + + if (lazy) + { + if (reference) + { + this._references.Add(key, ExtractInteger(value)); + } + else if (references) + { + this._multipleReferences.Add(key, ExtractCompoundInteger(value)); + } + else + { + throw new InvalidOperationException($"Getting here should be impossible! Key {key} is lazy, but not a reference"); + } + return; + } + + if (this.SectionProperties.StringKeys.Contains(key)) + { + property?.SetValue(this, ExtractString(value)); + } + else if (this.SectionProperties.EnumerationKeys.Contains(key)) + { + property?.SetValue(this, ExtractEnumeration(value)); + } + else if (this.SectionProperties.IntegerKeys.Contains(key)) + { + property?.SetValue(this, ExtractInteger(value)); + } + else if (this.SectionProperties.HexIntegerKeys.Contains(key)) + { + property?.SetValue(this, ExtractHexInteger(value)); + } + else if (this.SectionProperties.LongKeys.Contains(key)) + { + property?.SetValue(this, ExtractLong(value)); + } + else if (this.SectionProperties.HexLongKeys.Contains(key)) + { + property?.SetValue(this, ExtractHexLong(value)); + } + else if (this.SectionProperties.DateTimeKeys.Contains(key)) + { + property?.SetValue(this, ExtractDateTime(value)); + } + else if (this.SectionProperties.MultipleKeys.Contains(key)) + { + property?.SetValue(this, ExtractCompoundInteger(value)); + } + else + { + throw new InvalidOperationException($"Section: {key} has not been categorised"); + } + } + + protected static string ExtractString(string value) => value.Trim('"'); + protected static string ExtractEnumeration(string value) => value; + + private static T ExtractHexValue(string value) where T : INumberBase => T.Parse(value.AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + private static T ExtractNumericValue(string value) where T : IParsable => T.Parse(value, CultureInfo.InvariantCulture); + + protected static int ExtractHexInteger(string value) => ExtractHexValue(value); + protected static long ExtractHexLong(string value) => ExtractHexValue(value); + protected static int ExtractInteger(string value) => ExtractNumericValue(value); + protected static long ExtractLong(string value) => ExtractNumericValue(value); + protected static DateTime ExtractDateTime(string value) => _unixEpoch.AddSeconds(ExtractHexLong(value)); + protected static string[] ExtractCompoundString(string value) => value.Split('+'); + + protected static List ExtractCompoundInteger(string value) + { + var elements = ExtractCompoundString(value); + var returned = new List(elements.Length); + foreach (var element in elements) + { + returned.Add(ExtractInteger(element)); + } + return returned; + } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/SectionAttribute.cs b/M6502/M6502.Symbols/SectionAttribute.cs index 1ced0c4..9c790c9 100644 --- a/M6502/M6502.Symbols/SectionAttribute.cs +++ b/M6502/M6502.Symbols/SectionAttribute.cs @@ -1,16 +1,10 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + [AttributeUsage(AttributeTargets.Class)] + internal class SectionAttribute(string key, string? referencing = null) : Attribute { - namespace Symbols - { - [AttributeUsage(AttributeTargets.Class)] - internal class SectionAttribute(string key, string? referencing = null) : Attribute - { - public string Key { get; private set; } = key; + public string Key { get; private set; } = key; - public string? Referencing { get; private set; } = referencing; - } - } + public string? Referencing { get; private set; } = referencing; } } diff --git a/M6502/M6502.Symbols/SectionEnumerationAttribute.cs b/M6502/M6502.Symbols/SectionEnumerationAttribute.cs index 1d9aa7a..18fd2ba 100644 --- a/M6502/M6502.Symbols/SectionEnumerationAttribute.cs +++ b/M6502/M6502.Symbols/SectionEnumerationAttribute.cs @@ -1,13 +1,7 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + [AttributeUsage(AttributeTargets.Property)] + internal sealed class SectionEnumerationAttribute(string key) : SectionPropertyAttribute(key, enumeration: true) { - namespace Symbols - { - [AttributeUsage(AttributeTargets.Property)] - internal sealed class SectionEnumerationAttribute(string key) : SectionPropertyAttribute(key, enumeration: true) - { - } - } } } diff --git a/M6502/M6502.Symbols/SectionPropertyAttribute.cs b/M6502/M6502.Symbols/SectionPropertyAttribute.cs index 4edba6a..b8a0053 100644 --- a/M6502/M6502.Symbols/SectionPropertyAttribute.cs +++ b/M6502/M6502.Symbols/SectionPropertyAttribute.cs @@ -1,24 +1,18 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + [AttributeUsage(AttributeTargets.Property)] + internal class SectionPropertyAttribute(string key, System.Type? type = null, bool enumeration = false, bool hexadecimal = false, bool optional = false, bool many = false) : Attribute { - namespace Symbols - { - [AttributeUsage(AttributeTargets.Property)] - internal class SectionPropertyAttribute(string key, System.Type? type = null, bool enumeration = false, bool hexadecimal = false, bool optional = false, bool many = false) : Attribute - { - public string Key { get; private set; } = key; + public string Key { get; private set; } = key; - public System.Type? Type { get; private set; } = type; + public System.Type? Type { get; private set; } = type; - public bool Enumeration { get; private set; } = enumeration; + public bool Enumeration { get; private set; } = enumeration; - public bool Hexadecimal { get; private set; } = hexadecimal; + public bool Hexadecimal { get; private set; } = hexadecimal; - public bool Optional { get; private set; } = optional; + public bool Optional { get; private set; } = optional; - public bool Many { get; private set; } = many; - } - } + public bool Many { get; private set; } = many; } } diff --git a/M6502/M6502.Symbols/SectionReferenceAttribute.cs b/M6502/M6502.Symbols/SectionReferenceAttribute.cs index fa33b52..8d52ed2 100644 --- a/M6502/M6502.Symbols/SectionReferenceAttribute.cs +++ b/M6502/M6502.Symbols/SectionReferenceAttribute.cs @@ -1,13 +1,7 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + [AttributeUsage(AttributeTargets.Property)] + internal sealed class SectionReferenceAttribute(string key, bool optional = false) : SectionPropertyAttribute(key, type: typeof(int), optional: optional) { - namespace Symbols - { - [AttributeUsage(AttributeTargets.Property)] - internal sealed class SectionReferenceAttribute(string key, bool optional = false) : SectionPropertyAttribute(key, type: typeof(int), optional: optional) - { - } - } } } diff --git a/M6502/M6502.Symbols/SectionReferencesAttribute.cs b/M6502/M6502.Symbols/SectionReferencesAttribute.cs index 1071600..b0c682c 100644 --- a/M6502/M6502.Symbols/SectionReferencesAttribute.cs +++ b/M6502/M6502.Symbols/SectionReferencesAttribute.cs @@ -1,13 +1,7 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + [AttributeUsage(AttributeTargets.Property)] + internal sealed class SectionReferencesAttribute(string key) : SectionPropertyAttribute(key, many: true) { - namespace Symbols - { - [AttributeUsage(AttributeTargets.Property)] - internal sealed class SectionReferencesAttribute(string key) : SectionPropertyAttribute(key, many: true) - { - } - } } } diff --git a/M6502/M6502.Symbols/Segment.cs b/M6502/M6502.Symbols/Segment.cs index efa2dc4..2a9252e 100644 --- a/M6502/M6502.Symbols/Segment.cs +++ b/M6502/M6502.Symbols/Segment.cs @@ -1,31 +1,25 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + // seg id=1,name="RODATA",start=0x00F471,size=0x0000,addrsize=absolute,type=ro,oname="sudoku.65b",ooffs=1137 + [Section("seg", "Segments")] + public sealed class Segment(Parser container) : NamedSection(container) { - namespace Symbols - { - // seg id=1,name="RODATA",start=0x00F471,size=0x0000,addrsize=absolute,type=ro,oname="sudoku.65b",ooffs=1137 - [Section("seg", "Segments")] - public sealed class Segment(Parser container) : NamedSection(container) - { - [SectionProperty("start", hexadecimal: true)] - public int Start { get; private set; } + [SectionProperty("start", hexadecimal: true)] + public int Start { get; private set; } - [SectionProperty("size", hexadecimal: true)] - public int Size { get; private set; } + [SectionProperty("size", hexadecimal: true)] + public int Size { get; private set; } - [SectionEnumeration("addrsize")] - public string? AddressSize { get; private set; } + [SectionEnumeration("addrsize")] + public string? AddressSize { get; private set; } - [SectionEnumeration("type")] - public string? Type { get; private set; } + [SectionEnumeration("type")] + public string? Type { get; private set; } - [SectionProperty("oname")] - public string? OName { get; private set; } + [SectionProperty("oname")] + public string? OName { get; private set; } - [SectionProperty("ooffs")] - public int? OOFFS { get; private set; } - } - } + [SectionProperty("ooffs")] + public int? OOFFS { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Span.cs b/M6502/M6502.Symbols/Span.cs index 667edc2..6f27a92 100644 --- a/M6502/M6502.Symbols/Span.cs +++ b/M6502/M6502.Symbols/Span.cs @@ -1,25 +1,19 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + //span id = 351, seg = 7, start = 0, size = 2, type = 2 + [Section("span", "Spans")] + public sealed class Span(Parser container) : IdentifiableSection(container) { - namespace Symbols - { - //span id = 351, seg = 7, start = 0, size = 2, type = 2 - [Section("span", "Spans")] - public sealed class Span(Parser container) : IdentifiableSection(container) - { - [SectionReference("seg")] - public Symbols.Segment? Segment { get; private set; } + [SectionReference("seg")] + public Symbols.Segment? Segment { get; private set; } - [SectionProperty("start")] - public int Start { get; private set; } + [SectionProperty("start")] + public int Start { get; private set; } - [SectionProperty("size")] - public int Size { get; private set; } + [SectionProperty("size")] + public int Size { get; private set; } - [SectionReference("type")] - public Symbols.Type? Type { get; private set; } - } - } + [SectionReference("type")] + public Symbols.Type? Type { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Symbol.cs b/M6502/M6502.Symbols/Symbol.cs index 9523c0e..fc1c58b 100644 --- a/M6502/M6502.Symbols/Symbol.cs +++ b/M6502/M6502.Symbols/Symbol.cs @@ -1,39 +1,33 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + using System.Collections.Generic; + + // sym id = 16, name = "solve", addrsize = absolute, size = 274, scope = 0, def = 94,ref=144+17+351,val=0xF314,seg=6,type=lab + [Section("symbol", "Symbols")] + public sealed class Symbol(Parser container) : NamedSection(container) { - namespace Symbols - { - using System.Collections.Generic; + [SectionEnumeration("addrsize")] + public string? AddressSize { get; private set; } - // sym id = 16, name = "solve", addrsize = absolute, size = 274, scope = 0, def = 94,ref=144+17+351,val=0xF314,seg=6,type=lab - [Section("symbol", "Symbols")] - public sealed class Symbol(Parser container) : NamedSection(container) - { - [SectionEnumeration("addrsize")] - public string? AddressSize { get; private set; } + [SectionProperty("size")] + public int? Size { get; private set; } - [SectionProperty("size")] - public int? Size { get; private set; } + [SectionReference("scope")] + public Symbols.Scope? Scope { get; private set; } - [SectionReference("scope")] - public Symbols.Scope? Scope { get; private set; } + [SectionReferences("def")] + public List? Definitions { get; private set; } - [SectionReferences("def")] - public List? Definitions { get; private set; } + [SectionReferences("ref")] + public List? References { get; private set; } - [SectionReferences("ref")] - public List? References { get; private set; } + [SectionProperty("val", hexadecimal: true)] + public int Value { get; private set; } - [SectionProperty("val", hexadecimal: true)] - public int Value { get; private set; } + [SectionReference("seg")] + public Symbols.Segment? Segment { get; private set; } - [SectionReference("seg")] - public Symbols.Segment? Segment { get; private set; } - - [SectionEnumeration("type")] - public string? Type { get; private set; } - } - } + [SectionEnumeration("type")] + public string? Type { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Type.cs b/M6502/M6502.Symbols/Type.cs index 7bc5759..f07090d 100644 --- a/M6502/M6502.Symbols/Type.cs +++ b/M6502/M6502.Symbols/Type.cs @@ -1,16 +1,10 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + // type id = 0, val = "800920" + [Section("type", "Types")] + public sealed class Type(Parser container) : IdentifiableSection(container) { - namespace Symbols - { - // type id = 0, val = "800920" - [Section("type", "Types")] - public sealed class Type(Parser container) : IdentifiableSection(container) - { - [SectionProperty("val")] - public string? Value { get; private set; } - } - } + [SectionProperty("val")] + public string? Value { get; private set; } } } \ No newline at end of file diff --git a/M6502/M6502.Symbols/Version.cs b/M6502/M6502.Symbols/Version.cs index 1dbacaf..ed1ad66 100644 --- a/M6502/M6502.Symbols/Version.cs +++ b/M6502/M6502.Symbols/Version.cs @@ -1,19 +1,13 @@ -namespace EightBit +namespace EightBit.Files.Symbols { - namespace Files + //version major = 2, minor = 0 + [Section("version")] + public sealed class Version(Parser container) : Section(container) { - namespace Symbols - { - //version major = 2, minor = 0 - [Section("version")] - public sealed class Version(Parser container) : Section(container) - { - [SectionProperty("major")] - public int Major { get; private set; } + [SectionProperty("major")] + public int Major { get; private set; } - [SectionProperty("minor")] - public int Minor { get; private set; } - } - } + [SectionProperty("minor")] + public int Minor { get; private set; } } } \ No newline at end of file