Flatten symbols namespace

This commit is contained in:
Adrian Conlon
2024-10-07 19:34:48 +01:00
parent 8d9e603165
commit ed4b4e3736
20 changed files with 1037 additions and 1157 deletions

View File

@@ -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; }
}
}

View File

@@ -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<int> 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<int> 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<int> 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<int> 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);
}
}
}

View File

@@ -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<int>(key);
}
}
public int Count(string key) => this.GetValueT<int>(key);
}
}

View File

@@ -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<Span>? Spans { get; private set; }
}
}
[SectionReferences("span")]
public List<Span>? Spans { get; private set; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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) { }
}
}

View File

@@ -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<string, List<Dictionary<string, string>>> Parsed { get; } = [];
public List<Section> SectionEntries { get; } = [];
public List<File> Files = [];
public List<Line> Lines = [];
public List<Module> Modules = [];
public List<Segment> Segments = [];
public List<Span> Spans = [];
public List<Scope> Scopes = [];
public List<Symbol> Symbols = [];
public List<Type> Types = [];
// Symbol sub-types
public IEnumerable<Symbol> Labels => this.SelectSymbolsByType("lab");
public IEnumerable<Symbol> Equates => this.SelectSymbolsByType("equ");
// Scope clarification
public List<Scope> AddressableScopes = [];
// Scope cache for precomputed ranges
private readonly int?[] _scopeAddressCache = new int?[0x10000];
#endregion
#region Lookups
private static IEnumerable<T> SelectIdMatching<T>(int id, IEnumerable<T>? items) where T : IdentifiableSection => from item in items where item.ID == id select item;
private IEnumerable<Symbol> SelectSymbolsByType(string type) => from symbol in this.Symbols where symbol.Type == type select symbol;
#region Label lookup
private IEnumerable<Symbol> 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<Symbol> 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<Symbol> 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<string, List<Dictionary<string, string>>> Parsed { get; } = [];
var symbol = scope.Symbol;
Debug.Assert(symbol != null);
var start = symbol.Value;
public List<Section> SectionEntries { get; } = [];
public List<File> Files = [];
public List<Line> Lines = [];
public List<Module> Modules = [];
public List<Segment> Segments = [];
public List<Span> Spans = [];
public List<Scope> Scopes = [];
public List<Symbol> Symbols = [];
public List<Type> Types = [];
// Symbol sub-types
public IEnumerable<Symbol> Labels => this.SelectSymbolsByType("lab");
public IEnumerable<Symbol> Equates => this.SelectSymbolsByType("equ");
// Scope clarification
public List<Scope> AddressableScopes = [];
// Scope cache for precomputed ranges
private readonly int?[] _scopeAddressCache = new int?[0x10000];
#endregion
#region Lookups
private static IEnumerable<T> SelectIdMatching<T>(int id, IEnumerable<T>? items) where T : IdentifiableSection => from item in items where item.ID == id select item;
private IEnumerable<Symbol> SelectSymbolsByType(string type) => from symbol in this.Symbols where symbol.Type == type select symbol;
#region Label lookup
private IEnumerable<Symbol> 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<Symbol> 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<Symbol> 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<Scope> 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<Scope> EvaluateScope(Scope? start)
{
Debug.Assert(start != null);
var returned = new List<Scope>();
for (var current = start; current.Parent != null; current = current.Parent)
{
returned.Add(current);
}
return returned;
}
private static List<Scope> 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>("file", this.Files);
private void ExtractLines() => this.Extract<Line>("line", this.Lines);
private void ExtractModules() => this.Extract<Module>("mod", this.Modules);
private void ExtractSegments() => this.Extract<Segment>("seg", this.Segments);
private void ExtractSpans() => this.Extract<Span>("span", this.Spans);
private void ExtractScopes() => this.Extract<Scope>("scope", this.Scopes);
private void ExtractSymbols() => this.Extract<Symbol>("sym", this.Symbols);
private void ExtractTypes() => this.Extract<Type>("type", this.Types);
private void Extract<T>(string key, List<T> 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<T>(string key, List<T> into, List<Dictionary<string, string>> 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<T>(int id, List<Dictionary<string, string>> parsed, List<T> into) where T : IdentifiableSection
{
Debug.Assert(into.Count == id);
var information = parsed[id];
var entry = this.ExtractEntry<T>(information);
Debug.Assert(into.Capacity > id);
into.Add(entry);
}
private T ExtractEntry<T>(Dictionary<string, string> 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<Scope> 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<Scope> EvaluateScope(Scope? start)
{
Debug.Assert(start != null);
var returned = new List<Scope>();
for (var current = start; current.Parent != null; current = current.Parent)
{
returned.Add(current);
}
return returned;
}
private static List<Scope> 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<string, string> BuildDictionary(string[] parts)
{
var dictionary = new Dictionary<string, string>(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>("file", this.Files);
private void ExtractLines() => this.Extract<Line>("line", this.Lines);
private void ExtractModules() => this.Extract<Module>("mod", this.Modules);
private void ExtractSegments() => this.Extract<Segment>("seg", this.Segments);
private void ExtractSpans() => this.Extract<Span>("span", this.Spans);
private void ExtractScopes() => this.Extract<Scope>("scope", this.Scopes);
private void ExtractSymbols() => this.Extract<Symbol>("sym", this.Symbols);
private void ExtractTypes() => this.Extract<Type>("type", this.Types);
private void Extract<T>(string key, List<T> 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<T>(string key, List<T> into, List<Dictionary<string, string>> 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<T>(int id, List<Dictionary<string, string>> parsed, List<T> into) where T : IdentifiableSection
{
Debug.Assert(into.Count == id);
var information = parsed[id];
var entry = this.ExtractEntry<T>(information);
Debug.Assert(into.Capacity > id);
into.Add(entry);
}
private T ExtractEntry<T>(Dictionary<string, string> 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<string, string> BuildDictionary(string[] parts)
{
var dictionary = new Dictionary<string, string>(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
}
}

View File

@@ -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<string, string> _entriesToProperties = [];
public HashSet<string> StringKeys { get; } = [];
public HashSet<string> EnumerationKeys { get; } = [];
public HashSet<string> IntegerKeys { get; } = [];
public HashSet<string> HexIntegerKeys { get; } = [];
public HashSet<string> LongKeys { get; } = [];
public HashSet<string> HexLongKeys { get; } = [];
public HashSet<string> DateTimeKeys { get; } = [];
public HashSet<string> MultipleKeys { get; } = [];
public Dictionary<string, PropertyInfo> Properties { get; } = [];
internal SectionAttribute? ClassAttribute { get; private set; }
internal Dictionary<string, Tuple<string, System.Type>> ReferenceAttributes { get; } = [];
internal Dictionary<string, Tuple<string, System.Type>> 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<string, string> _entriesToProperties = [];
this.ProcessPropertyAttributes(property);
}
}
public HashSet<string> StringKeys { get; } = [];
public HashSet<string> EnumerationKeys { get; } = [];
public HashSet<string> IntegerKeys { get; } = [];
public HashSet<string> HexIntegerKeys { get; } = [];
public HashSet<string> LongKeys { get; } = [];
public HashSet<string> HexLongKeys { get; } = [];
public HashSet<string> DateTimeKeys { get; } = [];
public HashSet<string> 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<string, PropertyInfo> 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<string, Tuple<string, System.Type>> 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<string, Tuple<string, System.Type>> ReferencesAttributes { get; } = [];
public T? MaybeGetValue<T>(object? obj, string key) => (T?)this.GetValue(obj, key);
public void Build(System.Type type)
public T GetValueT<T>(object? obj, string key)
{
var possible = this.MaybeGetValue<T>(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<string, System.Type>(name, originalType));
} else if (attribute is SectionReferencesAttribute referencesAttribute)
{
this.ReferencesAttributes.Add(key, new Tuple<string, System.Type>(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<int>))
{
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<long>))
{
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<T>(object? obj, string key) => (T?)this.GetValue(obj, key);
public T GetValueT<T>(object? obj, string key)
{
var possible = this.MaybeGetValue<T>(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<string, System.Type>(name, originalType));
} else if (attribute is SectionReferencesAttribute referencesAttribute)
{
this.ReferencesAttributes.Add(key, new Tuple<string, System.Type>(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<int>))
{
if (hex)
{
this.AddHexIntegerKey(key);
}
else
{
this.AddIntegerKey(key);
}
}
else if (type == typeof(long) || type == typeof(Nullable<long>))
{
if (hex)
{
this.AddHexLongKey(key);
}
else
{
this.AddLongKey(key);
}
}
else if (type == typeof(DateTime) || type == typeof(Nullable<DateTime>))
{
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<DateTime>))
{
this.AddDateTimeKey(key);
}
else
{
throw new NotImplementedException($"Property type <{type}> has not been implemented");
}
}
}
}

View File

@@ -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<Span>? Spans { get; private set; }
}
}
[SectionReferences("span")]
public List<Span>? Spans { get; private set; }
}
}

View File

@@ -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<System.Type, ReflectedSectionProperties> _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<string, int> _references = [];
protected readonly Dictionary<string, List<int>> _multipleReferences = [];
protected Dictionary<string, string>? _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<System.Type, ReflectedSectionProperties> _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<string, int> _references = [];
protected readonly Dictionary<string, List<int>> _multipleReferences = [];
protected Dictionary<string, string>? _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<T>(string key) => this.SectionProperties.GetValueT<T>(this, key);
public virtual void Parse(Dictionary<string, string> 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<T>(string value) where T : INumberBase<T> => T.Parse(value.AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
private static T ExtractNumericValue<T>(string value) where T : IParsable<T> => T.Parse(value, CultureInfo.InvariantCulture);
protected static int ExtractHexInteger(string value) => ExtractHexValue<int>(value);
protected static long ExtractHexLong(string value) => ExtractHexValue<long>(value);
protected static int ExtractInteger(string value) => ExtractNumericValue<int>(value);
protected static long ExtractLong(string value) => ExtractNumericValue<long>(value);
protected static DateTime ExtractDateTime(string value) => _unixEpoch.AddSeconds(ExtractHexLong(value));
protected static string[] ExtractCompoundString(string value) => value.Split('+');
protected static List<int> ExtractCompoundInteger(string value)
{
var elements = ExtractCompoundString(value);
var returned = new List<int>(elements.Length);
foreach (var element in elements)
{
returned.Add(ExtractInteger(element));
}
return returned;
}
entry.Build(type);
}
}
public T GetValueT<T>(string key) => this.SectionProperties.GetValueT<T>(this, key);
public virtual void Parse(Dictionary<string, string> 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<T>(string value) where T : INumberBase<T> => T.Parse(value.AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
private static T ExtractNumericValue<T>(string value) where T : IParsable<T> => T.Parse(value, CultureInfo.InvariantCulture);
protected static int ExtractHexInteger(string value) => ExtractHexValue<int>(value);
protected static long ExtractHexLong(string value) => ExtractHexValue<long>(value);
protected static int ExtractInteger(string value) => ExtractNumericValue<int>(value);
protected static long ExtractLong(string value) => ExtractNumericValue<long>(value);
protected static DateTime ExtractDateTime(string value) => _unixEpoch.AddSeconds(ExtractHexLong(value));
protected static string[] ExtractCompoundString(string value) => value.Split('+');
protected static List<int> ExtractCompoundInteger(string value)
{
var elements = ExtractCompoundString(value);
var returned = new List<int>(elements.Length);
foreach (var element in elements)
{
returned.Add(ExtractInteger(element));
}
return returned;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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)
{
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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)
{
}
}
}
}

View File

@@ -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)
{
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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<Line>? Definitions { get; private set; }
[SectionReferences("def")]
public List<Line>? Definitions { get; private set; }
[SectionReferences("ref")]
public List<Line>? References { get; private set; }
[SectionReferences("ref")]
public List<Line>? 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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}