/* * 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); } /// /// Gets the value associated with the key, unless it's a variable. /// /// Label to look up. /// Symbol, or null if not found, or found but it's a variable. /// True if the key is present, false otherwise. 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; } /// /// Removes the specified symbol. /// public void Remove(Symbol sym) { mSymbols.Remove(sym.Label); mSymbolsByValue.Remove(sym); ChangeSerial++; } } }