2024-06-06 00:47:17 +01:00

419 lines
15 KiB
C#

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