/*
* 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.Generic;
using System.Diagnostics;
namespace SourceGen {
///
/// Table of redefinable variables. A project may have several of these, at different
/// offsets.
///
/// The class is mutable, but may only be modified by the LvTable editor (which makes
/// changes to a work object that moves through the undo/redo buffer) or the
/// deserializer.
///
///
/// The contents of later tables overwrite the contents of earlier tables. A
/// variable is replaced if the name is re-used (because a symbol can have only one
/// value at a time) or if the value is re-used (because they're applied automatically
/// and we need to know which symbol to use).
///
/// The DefSymbols should have symbol type Constant or ExternalAddr. These do not clash
/// with each other, e.g. the statements "LDA $10,S" and "STA $10" use two different
/// variables, because one is an 8-bit stack offset while the other is an 8-bit direct page
/// address.
///
/// (Referring to these as "local" variables is a bit of a misnomer, since they have
/// global scope from the point where they're defined. The name reflects their intended
/// usage, rather than how the assembler will treat them.)
///
public class LocalVariableTable {
///
/// If set, all values from previous VariableTables should be discarded when this
/// table is encountered.
///
///
/// This does not correspond to any output in generated assembly code. We simply stop
/// trying to associate the symbols with instructions. The code will either use a
/// less tightly-scoped value (e.g. project symbol) or output as hex. There is no need
/// to tell the assembler to forget the symbol.
///
/// It might be useful to allow addresses (DP ops) and constants (StackRel ops) to be
/// cleared independently, but I suspect the typical compiled-language scenario will
/// involve StackRel for args and a sliding DP for locals, so generally it makes
/// sense to just clear everything.
///
public bool ClearPrevious { get; set; }
///
/// List of variables, sorted by label.
///
private SortedList mVarByLabel;
///
/// List of variables. This is manually sorted when needed. The key is a combination
/// of the value and the symbol type, so we can't use a simple SortedList.
///
private List mVarByValue;
private bool mNeedSort = true;
///
/// Constructs an empty table.
///
public LocalVariableTable() {
mVarByLabel = new SortedList();
mVarByValue = new List();
}
///
/// Copy constructor.
///
/// Object to clone.
public LocalVariableTable(LocalVariableTable src) : this() {
ClearPrevious = src.ClearPrevious;
foreach (KeyValuePair kvp in src.mVarByLabel) {
mVarByLabel[kvp.Value.Label] = kvp.Value;
mVarByValue.Add(kvp.Value);
}
Debug.Assert(this == src);
}
///
/// Number of entries in the variable table.
///
public int Count { get { return mVarByLabel.Count; } }
///
/// Returns the Nth item, sorted by value. This is NOT a lookup by value.
///
public DefSymbol this[int index] {
get {
SortIfNeeded();
return mVarByValue[index];
}
}
private void SortIfNeeded() {
if (mNeedSort) {
// Currently sorting primarily by value, secondarily by symbol type. This
// ordering determines how it appears in the code list. If we want to make it
// configurable we just need to replace the sort function.
mVarByValue.Sort((a, b) => {
// Numeric ascending.
int diff = a.Value - b.Value;
if (diff != 0) {
return diff;
}
// DP addr first, StackRel const second
if (a.SymbolType == Symbol.Type.ExternalAddr) {
return -1;
} else {
return 1;
}
//return a.Label.CompareTo(b.Label);
});
mNeedSort = false;
}
}
public void Clear() {
mVarByLabel.Clear();
mVarByValue.Clear();
}
public DefSymbol GetByLabel(string label) {
return mVarByLabel[label];
}
public void RemoveByLabel(string label) {
if (mVarByLabel.TryGetValue(label, out DefSymbol defSym)) {
mVarByLabel.Remove(defSym.Label);
mVarByValue.Remove(defSym);
}
Debug.Assert(mVarByValue.Count == mVarByLabel.Count);
// Should not be necessary to re-sort the by-value list.
}
///
/// Finds symbols that overlap with the specified value and width. If more than one
/// matching symbol is found, an arbitrary match will be returned. Comparisons are
/// only performed between symbols of the same type, so addresses and constants do
/// not clash.
///
/// Value to compare.
/// Width to check.
/// One matching symbol, or null if none matched.
public DefSymbol GetByValueRange(int value, int width, Symbol.Type type) {
foreach (KeyValuePair kvp in mVarByLabel) {
if (DefSymbol.CheckOverlap(kvp.Value, value, width, type)) {
return kvp.Value;
}
}
return null;
}
///
/// Adds a symbol to the variable table. Existing entries with the same name or
/// overlapping values will be removed.
///
/// Symbol to add.
public void AddOrReplace(DefSymbol newSym) {
if (newSym.SymbolType != Symbol.Type.Constant &&
newSym.SymbolType != Symbol.Type.ExternalAddr) {
Debug.Assert(false, "Unexpected symbol type " + newSym.SymbolType);
return;
}
if (newSym.SymbolSource != Symbol.Source.Variable) {
Debug.Assert(false, "Unexpected symbol source " + newSym.SymbolSource);
return;
}
// Remove existing entries that match on label or value. The value check must
// take the width into account.
if (mVarByLabel.TryGetValue(newSym.Label, out DefSymbol labelSym)) {
mVarByLabel.Remove(labelSym.Label);
mVarByValue.Remove(labelSym);
}
// Inefficient, but the list should be small.
DefSymbol valSym;
while ((valSym = GetByValueRange(newSym.Value,
newSym.DataDescriptor.Length, newSym.SymbolType)) != null) {
mVarByLabel.Remove(valSym.Label);
mVarByValue.Remove(valSym);
}
mVarByLabel.Add(newSym.Label, newSym);
mVarByValue.Add(newSym);
Debug.Assert(mVarByValue.Count == mVarByLabel.Count);
mNeedSort = true;
}
///
/// Returns a reference to the sorted-by-label list. The caller must not modify it.
///
///
/// This exists primarily for EditDefSymbol, which wants a list of this type to
/// perform uniqueness checks.
///
public SortedList GetSortedByLabel() {
return mVarByLabel;
}
public static bool operator ==(LocalVariableTable a, LocalVariableTable b) {
if (ReferenceEquals(a, b)) {
return true; // same object, or both null
}
if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) {
return false; // one is null
}
// All fields must be equal.
if (a.ClearPrevious != b.ClearPrevious) {
return false;
}
if (a.mVarByLabel.Count != b.mVarByLabel.Count) {
return false;
}
// Compare all list entries.
for (int i = 0; i < a.mVarByLabel.Count; i++) {
if (a.mVarByLabel.Values[i] != b.mVarByLabel.Values[i]) {
return false;
}
}
return true;
}
public static bool operator !=(LocalVariableTable a, LocalVariableTable b) {
return !(a == b);
}
public override bool Equals(object obj) {
return obj is LocalVariableTable && this == (LocalVariableTable)obj;
}
public override int GetHashCode() {
int hashCode = 0;
foreach (KeyValuePair kvp in mVarByLabel) {
hashCode ^= kvp.Value.GetHashCode();
}
if (ClearPrevious) {
hashCode++;
}
return hashCode;
}
public void DebugDump() {
Debug.WriteLine("LocalVariableTable count=" + Count + " clear-previous=" +
ClearPrevious);
for (int i = 0; i < Count; i++) {
Debug.WriteLine(" " + i + ": " + this[i]);
}
}
}
}