/*
* 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 {
///
/// List of all symbols, arranged primarily by label, but also accessible by value. All
/// symbols have a unique label.
///
public class SymbolTable : IEnumerable {
///
/// Primary storage. Provides fast lookup by label. The StringComparer we choose
/// determines how case sensitivity and Culture is handled.
private SortedList mSymbols =
new SortedList(Asm65.Label.LABEL_COMPARER);
///
/// Same content, but ordered by value. Note the key and the value are the same object.
///
private SortedList mSymbolsByValue =
new SortedList(new CompareByValue());
///
/// 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.
///
private class CompareByValue : IComparer {
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);
}
}
///
/// 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.
///
public int ChangeSerial { get; private set; }
public SymbolTable() { }
// IEnumerable
public IEnumerator GetEnumerator() {
// .Values is documented as O(1)
return mSymbols.Values.GetEnumerator();
}
// IEnumerable
IEnumerator IEnumerable.GetEnumerator() {
return mSymbols.Values.GetEnumerator();
}
///
/// Clears the symbol table.
///
public void Clear() {
mSymbols.Clear();
mSymbolsByValue.Clear();
ChangeSerial++;
}
///
/// Returns the number of symbols in the table.
///
public int Count() {
Debug.Assert(mSymbolsByValue.Count == mSymbols.Count);
return mSymbols.Count;
}
///
/// Adds the specified symbol to the list. Throws an exception if the symbol is
/// already present.
///
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++;
}
///
/// 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.
///
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++;
}
}
///
/// Searches the table for symbols with matching address values. Ignores constants.
///
/// Value to find.
/// First matching symbol found, or null if nothing matched.
public Symbol FindAddressByValue(int value) {
// Get sorted list of values. This is documented as efficient.
IList 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;
}
///
/// Gets the value associated with the key.
///
/// Label to look up.
/// Symbol, or null if not found.
/// True if the key is present, false otherwise.
public bool TryGetValue(string key, out Symbol sym) {
return mSymbols.TryGetValue(key, out sym);
}
///
/// Removes the specified symbol.
///
public void Remove(Symbol sym) {
mSymbols.Remove(sym.Label);
mSymbolsByValue.Remove(sym);
ChangeSerial++;
}
}
}