1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-07-14 20:28:59 +00:00
6502bench/SourceGen/SymbolTable.cs
Andy McFadden 2828cc8ca7 Apply project/platform symbols to Numeric/Address data operands
A ".dd2 <address>" item would get linked to an internal label, but
references to external addresses weren't doing the appropriate
search through the platform/project symbol list.

This change altered the output of the 2019-local-variables test.
The previous behavior was restored by disabling "nearby" symbol
matching in the project properties.

Updated the "lookup symbol by address" function to ignore local
variables.

Also, minor updates to Applesoft and F8-ROM symbol tables.
2019-09-15 14:38:12 -07:00

233 lines
8.8 KiB
C#

/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace SourceGen {
/// <summary>
/// List of all symbols, arranged primarily by label, but also accessible by value. All
/// symbols have a unique label.
/// </summary>
public class SymbolTable : IEnumerable<Symbol> {
/// <summary>
/// Primary storage. Provides fast lookup by label. The StringComparer we choose
/// determines how case sensitivity and Culture is handled.
private SortedList<string, Symbol> mSymbols =
new SortedList<string, Symbol>(Asm65.Label.LABEL_COMPARER);
/// <summary>
/// Same content, but ordered by value. Note the key and the value are the same object.
/// </summary>
private SortedList<Symbol, Symbol> mSymbolsByValue =
new SortedList<Symbol, Symbol>(new CompareByValue());
/// <summary>
/// Compare two symbols, primarily by value, secondarily by source, and tertiarily
/// by label. The primary SortedList guarantees that the label is unique, so we
/// should never have two equal Symbols in the list.
///
/// The type comparison ensures that project symbols appear before platform symbols,
/// so that you can "overwrite" a platform symbol with the same value.
/// </summary>
private class CompareByValue : IComparer<Symbol> {
public int Compare(Symbol a, Symbol b) {
if (a.Value < b.Value) {
return -1;
} else if (a.Value > b.Value) {
return 1;
}
if ((int)a.SymbolSource < (int)b.SymbolSource) {
return -1;
} else if ((int)a.SymbolSource > (int)b.SymbolSource) {
return 1;
}
// Equal values, check string. We'll get a match on Remove or when
// replacing an entry with itself, but no two Symbols in the list
// should have the same label.
return Asm65.Label.LABEL_COMPARER.Compare(a.Label, b.Label);
}
}
/// <summary>
/// This is incremented whenever the contents of the symbol table change. External
/// code can compare this against a previous value to see if anything has changed
/// since the last visit.
///
/// We could theoretically miss something at the 2^32 rollover. Not worried.
/// </summary>
public int ChangeSerial { get; private set; }
public SymbolTable() { }
// IEnumerable
public IEnumerator<Symbol> GetEnumerator() {
// .Values is documented as O(1)
return mSymbols.Values.GetEnumerator();
}
// IEnumerable
IEnumerator IEnumerable.GetEnumerator() {
return mSymbols.Values.GetEnumerator();
}
/// <summary>
/// Clears the symbol table.
/// </summary>
public void Clear() {
mSymbols.Clear();
mSymbolsByValue.Clear();
ChangeSerial++;
}
/// <summary>
/// Returns the number of symbols in the table.
/// </summary>
public int Count() {
Debug.Assert(mSymbolsByValue.Count == mSymbols.Count);
return mSymbols.Count;
}
/// <summary>
/// Adds the specified symbol to the list. Throws an exception if the symbol is
/// already present.
/// </summary>
public void Add(Symbol sym) {
// If Symbol with matching label is in list, this will throw an exception,
// and the by-value add won't happen.
mSymbols.Add(sym.Label, sym);
mSymbolsByValue.Add(sym, sym);
ChangeSerial++;
}
/// <summary>
/// Finds the specified symbol by label. Throws an exception if it's not found.
///
/// Adds the specified symbol to the list, or replaces it if it's already present.
/// </summary>
public Symbol this[string key] {
get {
Debug.Assert(mSymbolsByValue.Count == mSymbols.Count);
return mSymbols[key];
}
set {
// Replacing {"foo", 1} with ("foo", 2} works correctly for mSymbols, because
// the label is the unique key. For mSymbolsByValue we have to explicitly
// remove it, because the entire Symbol is used as the key.
mSymbols.TryGetValue(key, out Symbol oldValue);
if (oldValue != null) {
mSymbolsByValue.Remove(oldValue);
}
mSymbols[key] = value;
mSymbolsByValue[value] = value;
ChangeSerial++;
}
}
/// <summary>
/// Searches the table for symbols with matching address values. Ignores constants and
/// variables.
/// </summary>
/// <param name="value">Value to find.</param>
/// <returns>First matching symbol found, or null if nothing matched.</returns>
public Symbol FindNonVariableByAddress(int value) {
// Get sorted list of values. This is documented as efficient.
IList<Symbol> values = mSymbolsByValue.Values;
//for (int i = 0; i < values.Count; i++) {
// if (values[i].Value == value && values[i].SymbolType != Symbol.Type.Constant) {
// return values[i];
// }
//}
int low = 0;
int high = values.Count - 1;
while (low <= high) {
int mid = (low + high) / 2;
Symbol midValue = values[mid];
if (midValue.Value == value) {
// found a match, walk back to find first match
while (mid > 0 && values[mid - 1].Value == value) {
mid--;
}
// now skip past constants and variables
while (mid < values.Count && (values[mid].SymbolType == Symbol.Type.Constant ||
values[mid].SymbolSource == Symbol.Source.Variable)) {
//Debug.WriteLine("disregarding " + values[mid]);
mid++;
}
if (mid < values.Count && values[mid].Value == value) {
return values[mid];
}
//Debug.WriteLine("Found value " + value + " but only constants");
return null;
} else if (midValue.Value < value) {
// move the low end in
low = mid + 1;
} else {
// move the high end in
Debug.Assert(midValue.Value > value);
high = mid - 1;
}
}
// not found
return null;
}
/// <summary>
/// Gets the value associated with the key.
/// </summary>
/// <param name="key">Label to look up.</param>
/// <param name="sym">Symbol, or null if not found.</param>
/// <returns>True if the key is present, false otherwise.</returns>
public bool TryGetValue(string key, out Symbol sym) {
return mSymbols.TryGetValue(key, out sym);
}
/// <summary>
/// Gets the value associated with the key, unless it's a variable.
/// </summary>
/// <param name="key">Label to look up.</param>
/// <param name="sym">Symbol, or null if not found, or found but it's a variable.</param>
/// <returns>True if the key is present, false otherwise.</returns>
public bool TryGetNonVariableValue(string key, out Symbol sym) {
bool found = mSymbols.TryGetValue(key, out sym);
if (found && sym.IsVariable) {
sym = null;
found = false;
}
return found;
}
/// <summary>
/// Removes the specified symbol.
/// </summary>
public void Remove(Symbol sym) {
mSymbols.Remove(sym.Label);
mSymbolsByValue.Remove(sym);
ChangeSerial++;
}
}
}