1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-19 06:31:02 +00:00
6502bench/SourceGen/SymbolTable.cs
Andy McFadden 97a372a884 Add selectable auto-label styles
SourceGen creates "auto" labels when it finds a reference to an
address that doesn't have a label associated with it.  The label for
address $1234 would be "L1234".  This change allows the project to
specify alternative label naming conventions, annotating them with
information from the cross-reference data.  For example, a subroutine
entry point (i.e. the target of a JSR) would be "S_1234".  (The
underscore was added to avoid confusion when an annotation letter
is the same as a hex digit.)

Also, tweaked the way the preferred clipboard line format is stored
in the settings file (was an integer, now an enumeration string).
2019-04-15 15:14:04 -07:00

216 lines
8.1 KiB
C#

/*
* Copyright 2018 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.
/// </summary>
/// <param name="value">Value to find.</param>
/// <returns>First matching symbol found, or null if nothing matched.</returns>
public Symbol FindAddressByValue(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
while (mid < values.Count && values[mid].SymbolType == Symbol.Type.Constant) {
//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>
/// Removes the specified symbol.
/// </summary>
public void Remove(Symbol sym) {
mSymbols.Remove(sym.Label);
mSymbolsByValue.Remove(sym);
ChangeSerial++;
}
}
}