mirror of
https://github.com/fadden/6502bench.git
synced 2024-09-11 20:54:37 +00:00
Implemented local variable editing. Operands that have a local variable reference, or are eligible to have one, can now be edited directly from the instruction operand edit dialog. Also, updated the code list double-click handler so that, if you double-click on the opcode of an instruction that uses a local variable reference, the selection and view will jump to the place where that variable was defined. Also, tweaked the way the References window refers to references to an address that didn't use a symbol at that address. Updated the explanation in the manual, which was a bit confusing. Also, fixed some odds and ends in the manual. Also, fixed a nasty infinite recursion bug (issue #47).
861 lines
36 KiB
C#
861 lines
36 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.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
|
|
using Asm65;
|
|
|
|
namespace SourceGen.WpfGui {
|
|
/// <summary>
|
|
/// Instruction operand editor.
|
|
/// </summary>
|
|
public partial class EditInstructionOperand : Window, INotifyPropertyChanged {
|
|
/// <summary>
|
|
/// Updated format descriptor. Will be null if the user selected "default".
|
|
/// </summary>
|
|
public FormatDescriptor FormatDescriptorResult { get; private set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Updated local variable table. Will be null if no changes were made.
|
|
/// </summary>
|
|
public LocalVariableTable LocalVariableResult { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Offset of the local variable table we updated in LocalVariableResult.
|
|
/// </summary>
|
|
public int LocalVariableTableOffsetResult { get; private set; }
|
|
|
|
private readonly string SYMBOL_NOT_USED;
|
|
private readonly string SYMBOL_UNKNOWN;
|
|
private readonly string SYMBOL_INVALID;
|
|
|
|
private readonly string CREATE_LOCAL_VARIABLE;
|
|
private readonly string EDIT_LOCAL_VARIABLE;
|
|
|
|
/// <summary>
|
|
/// Project reference.
|
|
/// </summary>
|
|
private DisasmProject mProject;
|
|
|
|
/// <summary>
|
|
/// Offset of instruction being edited.
|
|
/// </summary>
|
|
private int mOffset;
|
|
|
|
/// <summary>
|
|
/// Format object.
|
|
/// </summary>
|
|
private Formatter mFormatter;
|
|
|
|
/// <summary>
|
|
/// Operation definition, from file data.
|
|
/// </summary>
|
|
private OpDef mOpDef;
|
|
|
|
/// <summary>
|
|
/// Status flags at the point where the instruction is defined. This tells us whether
|
|
/// an operand is 8-bit or 16-bit.
|
|
/// </summary>
|
|
private StatusFlags mOpStatusFlags;
|
|
|
|
/// <summary>
|
|
/// Operand value, extracted from file data. For a relative branch, this will be
|
|
/// an address instead.
|
|
/// </summary>
|
|
private int mOperandValue;
|
|
|
|
/// <summary>
|
|
/// True when the input is valid. Controls whether the OK button is enabled.
|
|
/// </summary>
|
|
public bool IsValid {
|
|
get { return mIsValid; }
|
|
set { mIsValid = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsValid;
|
|
|
|
/// <summary>
|
|
/// Set when our load-time initialization is complete.
|
|
/// </summary>
|
|
private bool mLoadDone;
|
|
|
|
// INotifyPropertyChanged implementation
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="owner">Parent window.</param>
|
|
/// <param name="project">Project reference.</param>
|
|
/// <param name="offset">File offset of instruction start.</param>
|
|
/// <param name="formatter">Formatter object, for preview window.</param>
|
|
public EditInstructionOperand(Window owner, DisasmProject project, int offset,
|
|
Formatter formatter) {
|
|
InitializeComponent();
|
|
Owner = owner;
|
|
DataContext = this;
|
|
|
|
mProject = project;
|
|
mOffset = offset;
|
|
mFormatter = formatter;
|
|
|
|
SYMBOL_NOT_USED = (string)FindResource("str_SymbolNotUsed");
|
|
SYMBOL_INVALID = (string)FindResource("str_SymbolNotValid");
|
|
SYMBOL_UNKNOWN = (string)FindResource("str_SymbolUnknown");
|
|
|
|
CREATE_LOCAL_VARIABLE = (string)FindResource("str_CreateLocalVariable");
|
|
EDIT_LOCAL_VARIABLE = (string)FindResource("str_EditLocalVariable");
|
|
|
|
Debug.Assert(offset >= 0 && offset < project.FileDataLength);
|
|
mOpDef = project.CpuDef.GetOpDef(project.FileData[offset]);
|
|
Anattrib attr = project.GetAnattrib(offset);
|
|
mOpStatusFlags = attr.StatusFlags;
|
|
Debug.Assert(offset + mOpDef.GetLength(mOpStatusFlags) <= project.FileDataLength);
|
|
|
|
if (attr.OperandAddress >= 0) {
|
|
// Use this as the operand value when available. This lets us present
|
|
// relative branch instructions in the expected form.
|
|
mOperandValue = attr.OperandAddress;
|
|
} else {
|
|
// For BlockMove this will have both parts.
|
|
mOperandValue = mOpDef.GetOperand(project.FileData, offset, attr.StatusFlags);
|
|
}
|
|
}
|
|
|
|
private void Window_Loaded(object sender, RoutedEventArgs e) {
|
|
BasicFormat_Loaded();
|
|
NumericRefs_Loaded();
|
|
mLoadDone = true;
|
|
}
|
|
|
|
private void Window_ContentRendered(object sender, EventArgs e) {
|
|
UpdateControls();
|
|
symbolTextBox.SelectAll();
|
|
symbolTextBox.Focus();
|
|
}
|
|
|
|
private void OkButton_Click(object sender, RoutedEventArgs e) {
|
|
FormatDescriptorResult = CreateDescriptorFromControls();
|
|
|
|
// Export the updated local variable table if we made changes.
|
|
if (mEditedLvTable != null) {
|
|
LocalVariableTable lvt = mProject.LvTables[mLvTableOffset];
|
|
if (mEditedLvTable != lvt) {
|
|
LocalVariableResult = mEditedLvTable;
|
|
LocalVariableTableOffsetResult = mLvTableOffset;
|
|
|
|
Debug.WriteLine("NEW TABLE:");
|
|
mEditedLvTable.DebugDump(mLvTableOffset);
|
|
} else {
|
|
Debug.WriteLine("No change to LvTable, not exporting");
|
|
}
|
|
}
|
|
DialogResult = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the state of the UI controls as the user interacts with the dialog.
|
|
/// </summary>
|
|
private void UpdateControls() {
|
|
if (!mLoadDone) {
|
|
return;
|
|
}
|
|
|
|
// Parts panel IsEnabled depends directly on formatSymbolButton.IsChecked.
|
|
IsValid = true;
|
|
IsSymbolAuto = false;
|
|
IsPartPanelEnabled = false;
|
|
SymbolValueDecimal = string.Empty;
|
|
if (FormatSymbol) {
|
|
IsPartPanelEnabled = mOpDef.IsExtendedImmediate;
|
|
|
|
if (!Asm65.Label.ValidateLabel(SymbolLabel)) {
|
|
SymbolValueHex = SYMBOL_INVALID;
|
|
IsValid = false;
|
|
} else if (mProject.SymbolTable.TryGetValue(SymbolLabel, out Symbol sym)) {
|
|
if (sym.SymbolSource == Symbol.Source.Auto) {
|
|
// We try to block references to auto labels, but it's possible to get
|
|
// around it because FormatDescriptors are weak references (replace auto
|
|
// label with user label, reference non-existent auto label, remove user
|
|
// label). We could try harder, but currently not necessary.
|
|
IsValid = false;
|
|
IsSymbolAuto = true;
|
|
}
|
|
|
|
SymbolValueHex = mFormatter.FormatHexValue(sym.Value, 4);
|
|
SymbolValueDecimal = mFormatter.FormatDecimalValue(sym.Value);
|
|
} else {
|
|
// Valid but unknown symbol. This is fine -- symbols don't have to exist.
|
|
SymbolValueHex = SYMBOL_UNKNOWN;
|
|
}
|
|
} else {
|
|
SymbolValueHex = SYMBOL_NOT_USED;
|
|
}
|
|
|
|
UpdatePreview();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the contents of the preview text box.
|
|
/// </summary>
|
|
private void UpdatePreview() {
|
|
// Generate a descriptor from the controls. This isn't strictly necessary, but it
|
|
// gets all of the data in one small package.
|
|
FormatDescriptor dfd = CreateDescriptorFromControls();
|
|
|
|
if (dfd == null) {
|
|
// Showing the right thing for the default format is surprisingly hard. There
|
|
// are a bunch of complicated steps that are performed in sequence, including
|
|
// the "nearby label" lookups, the elision of hidden symbols, and other
|
|
// obscure bits that may get tweaked from time to time. These things are not
|
|
// easy to factor out because we're slicing the data at a different angle: the
|
|
// initial pass walks the entire file looking for one thing at a point before
|
|
// analysis has completed, while here we're trying to mimic all of the
|
|
// steps for a single offset, after analysis has finished. It's a lot of work
|
|
// to show text that they'll see as soon as they hit "OK".
|
|
PreviewText = string.Empty;
|
|
return;
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder(16);
|
|
|
|
// Show the opcode. Don't bother trying to figure out width disambiguation here.
|
|
sb.Append(mFormatter.FormatOpcode(mOpDef, OpDef.WidthDisambiguation.None));
|
|
sb.Append(' ');
|
|
|
|
bool showHashPrefix = mOpDef.IsImmediate ||
|
|
mOpDef.AddrMode == OpDef.AddressMode.BlockMove;
|
|
if (showHashPrefix) {
|
|
sb.Append('#');
|
|
}
|
|
|
|
Anattrib attr = mProject.GetAnattrib(mOffset);
|
|
int previewHexDigits = (attr.Length - 1) * 2;
|
|
int operandValue = mOperandValue;
|
|
bool isPcRelative = false;
|
|
bool isBlockMove = false;
|
|
if (attr.OperandAddress >= 0) {
|
|
if (mOpDef.AddrMode == OpDef.AddressMode.PCRel) {
|
|
previewHexDigits = 4; // show branches as $xxxx even when on zero page
|
|
isPcRelative = true;
|
|
} else if (mOpDef.AddrMode == OpDef.AddressMode.PCRelLong ||
|
|
mOpDef.AddrMode == OpDef.AddressMode.StackPCRelLong) {
|
|
isPcRelative = true;
|
|
}
|
|
} else {
|
|
if (mOpDef.AddrMode == OpDef.AddressMode.BlockMove) {
|
|
// MVN and MVP screw things up by having two operands in one instruction.
|
|
// We deal with this by passing in the value from the second byte
|
|
// (source bank) as the value, and applying the chosen format to both bytes.
|
|
isBlockMove = true;
|
|
operandValue = mOperandValue >> 8;
|
|
previewHexDigits = 2;
|
|
}
|
|
}
|
|
|
|
switch (dfd.FormatSubType) {
|
|
case FormatDescriptor.SubType.Hex:
|
|
sb.Append(mFormatter.FormatHexValue(operandValue, previewHexDigits));
|
|
break;
|
|
case FormatDescriptor.SubType.Decimal:
|
|
sb.Append(mFormatter.FormatDecimalValue(operandValue));
|
|
break;
|
|
case FormatDescriptor.SubType.Binary:
|
|
sb.Append(mFormatter.FormatBinaryValue(operandValue, 8));
|
|
break;
|
|
case FormatDescriptor.SubType.Ascii:
|
|
case FormatDescriptor.SubType.HighAscii:
|
|
case FormatDescriptor.SubType.C64Petscii:
|
|
case FormatDescriptor.SubType.C64Screen:
|
|
CharEncoding.Encoding enc = PseudoOp.SubTypeToEnc(dfd.FormatSubType);
|
|
sb.Append(mFormatter.FormatCharacterValue(operandValue, enc));
|
|
break;
|
|
case FormatDescriptor.SubType.Symbol:
|
|
if (mProject.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) {
|
|
// Block move is a little weird. "MVN label1,label2" is supposed to use
|
|
// the bank byte, while "MVN #const1,#const2" uses the entire symbol.
|
|
// The easiest thing to do is require the user to specify the "bank"
|
|
// part for 24-bit symbols, and always generate this as an immediate.
|
|
//
|
|
// MVN/MVP are also the only instructions with two operands, something
|
|
// we don't really handle.
|
|
// TODO(someday): allow a different symbol for each part of the operand.
|
|
|
|
// Hack to make relative branches look right in the preview window.
|
|
// Otherwise they show up like "<LABEL" because they appear to be
|
|
// only 8 bits.
|
|
int operandLen = dfd.Length - 1;
|
|
if (operandLen == 1 && isPcRelative) {
|
|
operandLen = 2;
|
|
}
|
|
|
|
// Set the operand length to 1 for block move so we use the part
|
|
// operators (<, >, ^) rather than bit-shifting.
|
|
if (isBlockMove) {
|
|
operandLen = 1;
|
|
}
|
|
|
|
PseudoOp.FormatNumericOpFlags flags;
|
|
if (isPcRelative) {
|
|
flags = PseudoOp.FormatNumericOpFlags.IsPcRel;
|
|
} else if (showHashPrefix) {
|
|
flags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
|
|
} else {
|
|
flags = PseudoOp.FormatNumericOpFlags.None;
|
|
}
|
|
string str = PseudoOp.FormatNumericOperand(mFormatter,
|
|
mProject.SymbolTable, null, dfd,
|
|
operandValue, operandLen, flags);
|
|
sb.Append(str);
|
|
|
|
if (sym.SymbolSource == Symbol.Source.Auto) {
|
|
mIsSymbolAuto = true;
|
|
}
|
|
} else {
|
|
sb.Append(dfd.SymbolRef.Label + " (?)");
|
|
Debug.Assert(!string.IsNullOrEmpty(dfd.SymbolRef.Label));
|
|
//symbolValueLabel.Text = Properties.Resources.MSG_SYMBOL_NOT_FOUND;
|
|
}
|
|
break;
|
|
default:
|
|
Debug.Assert(false);
|
|
sb.Append("BUG");
|
|
break;
|
|
}
|
|
|
|
if (isBlockMove) {
|
|
sb.Append(",#<dest>");
|
|
}
|
|
|
|
PreviewText = sb.ToString();
|
|
}
|
|
|
|
#region Basic Format
|
|
|
|
public bool FormatDefault {
|
|
get { return mFormatDefault; }
|
|
set { mFormatDefault = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatDefault;
|
|
|
|
public bool FormatHex {
|
|
get { return mFormatHex; }
|
|
set { mFormatHex = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatHex;
|
|
|
|
public bool FormatDecimal {
|
|
get { return mFormatDecimal; }
|
|
set { mFormatDecimal = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatDecimal;
|
|
|
|
public bool FormatBinary {
|
|
get { return mFormatBinary; }
|
|
set { mFormatBinary = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatBinary;
|
|
|
|
public bool IsFormatAsciiAllowed {
|
|
get { return mIsFormatAsciiAllowed; }
|
|
set { mIsFormatAsciiAllowed = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsFormatAsciiAllowed;
|
|
|
|
public bool FormatAscii {
|
|
get { return mFormatAscii; }
|
|
set { mFormatAscii = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatAscii;
|
|
|
|
public bool IsFormatPetsciiAllowed {
|
|
get { return mIsFormatPetsciiAllowed; }
|
|
set { mIsFormatPetsciiAllowed = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsFormatPetsciiAllowed;
|
|
|
|
public bool FormatPetscii {
|
|
get { return mFormatPetscii; }
|
|
set { mFormatPetscii = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatPetscii;
|
|
|
|
public bool IsFormatScreenCodeAllowed {
|
|
get { return mIsFormatScreenCodeAllowed; }
|
|
set { mIsFormatScreenCodeAllowed = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsFormatScreenCodeAllowed;
|
|
|
|
public bool FormatScreenCode {
|
|
get { return mFormatScreenCode; }
|
|
set { mFormatScreenCode = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatScreenCode;
|
|
|
|
public bool FormatSymbol {
|
|
get { return mFormatSymbol; }
|
|
set { mFormatSymbol = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatSymbol;
|
|
|
|
public string SymbolLabel {
|
|
get { return mSymbolLabel; }
|
|
set {
|
|
mSymbolLabel = value;
|
|
OnPropertyChanged();
|
|
// Set the radio button when the user starts typing.
|
|
if (mLoadDone) {
|
|
FormatSymbol = true;
|
|
}
|
|
UpdateControls();
|
|
}
|
|
}
|
|
private string mSymbolLabel;
|
|
|
|
public bool IsSymbolAuto {
|
|
get { return mIsSymbolAuto; }
|
|
set { mIsSymbolAuto = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsSymbolAuto;
|
|
|
|
public string SymbolValueHex {
|
|
get { return mSymbolValueHex; }
|
|
set { mSymbolValueHex = value; OnPropertyChanged(); }
|
|
}
|
|
private string mSymbolValueHex;
|
|
|
|
public string SymbolValueDecimal {
|
|
get { return mSymbolValueDecimal; }
|
|
set { mSymbolValueDecimal = value; OnPropertyChanged(); }
|
|
}
|
|
private string mSymbolValueDecimal;
|
|
|
|
public bool IsPartPanelEnabled {
|
|
get { return mIsPartPanelEnabled; }
|
|
set { mIsPartPanelEnabled = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsPartPanelEnabled;
|
|
|
|
public bool FormatPartLow {
|
|
get { return mFormatPartLow; }
|
|
set { mFormatPartLow = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatPartLow;
|
|
|
|
public bool FormatPartHigh {
|
|
get { return mFormatPartHigh; }
|
|
set { mFormatPartHigh = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatPartHigh;
|
|
|
|
public bool FormatPartBank {
|
|
get { return mFormatPartBank; }
|
|
set { mFormatPartBank = value; OnPropertyChanged(); UpdateControls(); }
|
|
}
|
|
private bool mFormatPartBank;
|
|
|
|
public string PreviewText {
|
|
get { return mPreviewText; }
|
|
set { mPreviewText = value; OnPropertyChanged(); }
|
|
}
|
|
private string mPreviewText;
|
|
|
|
/// <summary>
|
|
/// Configures the basic formatting options, based on the existing format descriptor.
|
|
/// </summary>
|
|
private void BasicFormat_Loaded() {
|
|
// Can this be represented as a character? We only allow the printable set
|
|
// here, not the extended set (which includes control characters).
|
|
if (mOperandValue == (byte) mOperandValue) {
|
|
IsFormatAsciiAllowed =
|
|
CharEncoding.IsPrintableLowOrHighAscii((byte)mOperandValue);
|
|
IsFormatPetsciiAllowed =
|
|
CharEncoding.IsPrintableC64Petscii((byte)mOperandValue);
|
|
IsFormatScreenCodeAllowed =
|
|
CharEncoding.IsPrintableC64ScreenCode((byte)mOperandValue);
|
|
} else {
|
|
IsFormatAsciiAllowed = IsFormatPetsciiAllowed = IsFormatScreenCodeAllowed =
|
|
false;
|
|
}
|
|
|
|
SymbolLabel = string.Empty;
|
|
FormatPartLow = true; // could default to high for MVN/MVP
|
|
FormatDefault = true; // if nothing better comes along
|
|
|
|
// Is there an operand format at this location? If not, we're done.
|
|
if (!mProject.OperandFormats.TryGetValue(mOffset, out FormatDescriptor dfd)) {
|
|
return;
|
|
}
|
|
|
|
// NOTE: it's entirely possible to have a weird format (e.g. string) if the
|
|
// instruction used to be hinted as data. Handle it gracefully.
|
|
switch (dfd.FormatType) {
|
|
case FormatDescriptor.Type.NumericLE:
|
|
switch (dfd.FormatSubType) {
|
|
case FormatDescriptor.SubType.Hex:
|
|
FormatHex = true;
|
|
break;
|
|
case FormatDescriptor.SubType.Decimal:
|
|
FormatDecimal = true;
|
|
break;
|
|
case FormatDescriptor.SubType.Binary:
|
|
FormatBinary = true;
|
|
break;
|
|
case FormatDescriptor.SubType.Ascii:
|
|
case FormatDescriptor.SubType.HighAscii:
|
|
if (IsFormatAsciiAllowed) {
|
|
FormatAscii = true;
|
|
}
|
|
break;
|
|
case FormatDescriptor.SubType.C64Petscii:
|
|
if (IsFormatPetsciiAllowed) {
|
|
FormatPetscii = true;
|
|
}
|
|
break;
|
|
case FormatDescriptor.SubType.C64Screen:
|
|
if (IsFormatScreenCodeAllowed) {
|
|
FormatScreenCode = true;
|
|
}
|
|
break;
|
|
case FormatDescriptor.SubType.Symbol:
|
|
Debug.Assert(dfd.HasSymbol);
|
|
FormatSymbol = true;
|
|
switch (dfd.SymbolRef.ValuePart) {
|
|
case WeakSymbolRef.Part.Low:
|
|
FormatPartLow = true;
|
|
break;
|
|
case WeakSymbolRef.Part.High:
|
|
FormatPartHigh = true;
|
|
break;
|
|
case WeakSymbolRef.Part.Bank:
|
|
FormatPartBank = true;
|
|
break;
|
|
default:
|
|
Debug.Assert(false);
|
|
break;
|
|
}
|
|
SymbolLabel = dfd.SymbolRef.Label;
|
|
break;
|
|
case FormatDescriptor.SubType.None:
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case FormatDescriptor.Type.NumericBE:
|
|
case FormatDescriptor.Type.StringGeneric:
|
|
case FormatDescriptor.Type.StringReverse:
|
|
case FormatDescriptor.Type.StringNullTerm:
|
|
case FormatDescriptor.Type.StringL8:
|
|
case FormatDescriptor.Type.StringL16:
|
|
case FormatDescriptor.Type.StringDci:
|
|
case FormatDescriptor.Type.Dense:
|
|
case FormatDescriptor.Type.Fill:
|
|
default:
|
|
// Unexpected; used to be data?
|
|
break;
|
|
}
|
|
|
|
// In theory, if FormatDefault is still checked, we failed to find a useful match
|
|
// for the format descriptor. In practice, the radio button checkification stuff
|
|
// happens later. If we want to tell the user that there's a bad descriptor present,
|
|
// we'll need to track it locally, or test all known radio buttons for True.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a FormatDescriptor from the current state of the dialog controls.
|
|
/// </summary>
|
|
/// <returns>New FormatDescriptor. Will return null if the default format is
|
|
/// selected, or symbol is selected with an empty label.</returns>
|
|
private FormatDescriptor CreateDescriptorFromControls() {
|
|
int instructionLength = mProject.GetAnattrib(mOffset).Length;
|
|
|
|
if (FormatSymbol) {
|
|
if (string.IsNullOrEmpty(SymbolLabel)) {
|
|
// empty symbol --> default format (intuitive way to delete label reference)
|
|
return null;
|
|
}
|
|
WeakSymbolRef.Part part;
|
|
if (FormatPartLow) {
|
|
part = WeakSymbolRef.Part.Low;
|
|
} else if (FormatPartHigh) {
|
|
part = WeakSymbolRef.Part.High;
|
|
} else if (FormatPartBank) {
|
|
part = WeakSymbolRef.Part.Bank;
|
|
} else {
|
|
Debug.Assert(false);
|
|
part = WeakSymbolRef.Part.Low;
|
|
}
|
|
return FormatDescriptor.Create(instructionLength,
|
|
new WeakSymbolRef(SymbolLabel, part), false);
|
|
}
|
|
|
|
FormatDescriptor.SubType subType;
|
|
if (FormatDefault) {
|
|
return null;
|
|
} else if (FormatHex) {
|
|
subType = FormatDescriptor.SubType.Hex;
|
|
} else if (FormatDecimal) {
|
|
subType = FormatDescriptor.SubType.Decimal;
|
|
} else if (FormatBinary) {
|
|
subType = FormatDescriptor.SubType.Binary;
|
|
} else if (FormatAscii) {
|
|
if (mOperandValue > 0x7f) {
|
|
subType = FormatDescriptor.SubType.HighAscii;
|
|
} else {
|
|
subType = FormatDescriptor.SubType.Ascii;
|
|
}
|
|
} else if (FormatPetscii) {
|
|
subType = FormatDescriptor.SubType.C64Petscii;
|
|
} else if (FormatScreenCode) {
|
|
subType = FormatDescriptor.SubType.C64Screen;
|
|
} else {
|
|
Debug.Assert(false);
|
|
subType = FormatDescriptor.SubType.None;
|
|
}
|
|
|
|
return FormatDescriptor.Create(instructionLength,
|
|
FormatDescriptor.Type.NumericLE, subType);
|
|
}
|
|
|
|
#endregion Basic Format
|
|
|
|
#region Numeric References
|
|
|
|
public bool ShowLvNotApplicable {
|
|
get { return mShowLvNotApplicable; }
|
|
set { mShowLvNotApplicable = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mShowLvNotApplicable;
|
|
|
|
public bool ShowLvTableNotFound {
|
|
get { return mShowLvTableNotFound; }
|
|
set { mShowLvTableNotFound = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mShowLvTableNotFound;
|
|
|
|
public bool ShowLvNoMatchFound {
|
|
get { return mShowLvNoMatchFound; }
|
|
set { mShowLvNoMatchFound = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mShowLvNoMatchFound;
|
|
|
|
public bool ShowLvMatchFound {
|
|
get { return mShowLvMatchFound; }
|
|
set { mShowLvMatchFound = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mShowLvMatchFound;
|
|
|
|
public string LocalVariableLabel {
|
|
get { return mLocalVariableLabel; }
|
|
set { mLocalVariableLabel = value; OnPropertyChanged(); }
|
|
}
|
|
private string mLocalVariableLabel;
|
|
|
|
public bool ShowLvCreateEditButton {
|
|
get { return mShowLvCreateEditButton; }
|
|
set { mShowLvCreateEditButton = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mShowLvCreateEditButton;
|
|
|
|
public string CreateEditLocalVariableText {
|
|
get { return mCreateEditLocalVariableText; }
|
|
set { mCreateEditLocalVariableText = value; OnPropertyChanged(); }
|
|
}
|
|
private string mCreateEditLocalVariableText;
|
|
|
|
/// <summary>
|
|
/// Offset of LocalVariableTable we're going to modify.
|
|
/// </summary>
|
|
private int mLvTableOffset;
|
|
|
|
/// <summary>
|
|
/// Local variable value. If there's already a definition, this will be pre-filled
|
|
/// with the current contents. Otherwise it will be null.
|
|
/// </summary>
|
|
private DefSymbol mEditedLocalVar;
|
|
|
|
/// <summary>
|
|
/// Clone of original table, with local edits.
|
|
/// </summary>
|
|
private LocalVariableTable mEditedLvTable;
|
|
|
|
|
|
/// <summary>
|
|
/// Configures the UI on the bottom half of the dialog.
|
|
/// </summary>
|
|
private void NumericRefs_Loaded() {
|
|
if (mOpDef.IsDirectPageInstruction || mOpDef.IsStackRelInstruction) {
|
|
LocalVariableLookup lvLookup =
|
|
new LocalVariableLookup(mProject.LvTables, mProject, false);
|
|
|
|
// If the operand is already a local variable, use whichever one the
|
|
// analyzer found.
|
|
Anattrib attr = mProject.GetAnattrib(mOffset);
|
|
if (attr.DataDescriptor != null && attr.DataDescriptor.HasSymbol &&
|
|
attr.DataDescriptor.SymbolRef.IsVariable) {
|
|
// Select the table that defines the local variable that's currently
|
|
// associated with this operand.
|
|
mLvTableOffset = lvLookup.GetDefiningTableOffset(mOffset,
|
|
attr.DataDescriptor.SymbolRef);
|
|
Debug.Assert(mLvTableOffset >= 0);
|
|
Debug.WriteLine("Symbol " + attr.DataDescriptor.SymbolRef +
|
|
" from var table at +" + mLvTableOffset.ToString("x6"));
|
|
} else {
|
|
// Operand is not a local variable. Find the closest table.
|
|
mLvTableOffset = lvLookup.GetNearestTableOffset(mOffset);
|
|
Debug.WriteLine("Closest table is at +" + mLvTableOffset.ToString("x6"));
|
|
}
|
|
|
|
if (mLvTableOffset < 0) {
|
|
ShowLvTableNotFound = true;
|
|
} else {
|
|
// Found a table. Do we have a matching symbol?
|
|
ShowLvCreateEditButton = true;
|
|
mEditedLocalVar = lvLookup.GetSymbol(mOffset, mOperandValue,
|
|
mOpDef.IsDirectPageInstruction ?
|
|
Symbol.Type.ExternalAddr : Symbol.Type.Constant);
|
|
if (mEditedLocalVar == null) {
|
|
ShowLvNoMatchFound = true;
|
|
CreateEditLocalVariableText = CREATE_LOCAL_VARIABLE;
|
|
} else {
|
|
ShowLvMatchFound = true;
|
|
CreateEditLocalVariableText = EDIT_LOCAL_VARIABLE;
|
|
LocalVariableLabel = mEditedLocalVar.Label;
|
|
}
|
|
|
|
// We need to update the symbol table while we work to make the uniqueness
|
|
// check come out right. Otherwise if you edit, rename FOO to BAR,
|
|
// then edit again, you won't be able to rename BAR back to FOO because
|
|
// it's already in the list and it's not self.
|
|
//
|
|
// We don't need the full LVT, just the list of symbols, but we'll want
|
|
// to hand the modified table to the caller when we exit.
|
|
LocalVariableTable lvt = mProject.LvTables[mLvTableOffset];
|
|
mEditedLvTable = new LocalVariableTable(lvt);
|
|
}
|
|
} else {
|
|
ShowLvNotApplicable = true;
|
|
}
|
|
}
|
|
|
|
private void EditLocalVariableButton_Click(object sender, RoutedEventArgs e) {
|
|
Debug.Assert(mOpDef.IsDirectPageInstruction || mOpDef.IsStackRelInstruction);
|
|
Debug.Assert(mLvTableOffset >= 0);
|
|
|
|
DefSymbol initialVar = mEditedLocalVar;
|
|
|
|
if (initialVar == null) {
|
|
Symbol.Type symType;
|
|
if (mOpDef.IsDirectPageInstruction) {
|
|
symType = Symbol.Type.ExternalAddr;
|
|
} else {
|
|
symType = Symbol.Type.Constant;
|
|
}
|
|
|
|
// We need to pre-load the value and type, but we can't create a symbol with
|
|
// an empty name. We don't really need to create something unique since the
|
|
// dialog will handle it.
|
|
initialVar = new DefSymbol("VAR", mOperandValue,
|
|
Symbol.Source.Variable, symType, FormatDescriptor.SubType.None,
|
|
string.Empty, string.Empty, 1);
|
|
}
|
|
|
|
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,
|
|
mEditedLvTable.GetSortedByLabel(), initialVar, mProject.SymbolTable,
|
|
true, true);
|
|
if (dlg.ShowDialog() == true) {
|
|
if (mEditedLocalVar != dlg.NewSym) {
|
|
// Integrate result. Future edits will start with this.
|
|
// We can't delete a symbol, just create or modify.
|
|
Debug.Assert(dlg.NewSym != null);
|
|
mEditedLocalVar = dlg.NewSym;
|
|
mEditedLvTable.AddOrReplace(dlg.NewSym);
|
|
LocalVariableLabel = mEditedLocalVar.Label;
|
|
CreateEditLocalVariableText = EDIT_LOCAL_VARIABLE;
|
|
} else {
|
|
Debug.WriteLine("No change to def symbol, ignoring edit");
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Numeric References
|
|
|
|
#if false
|
|
/// <summary>
|
|
/// Configures the buttons in the "symbol shortcuts" group box. The entire box is
|
|
/// disabled unless "symbol" is selected. Other options are selectively enabled or
|
|
/// disabled as appropriate for the current input. If we disable the selection option,
|
|
/// the selection will be reset to default.
|
|
/// </summary>
|
|
private void ConfigureSymbolShortcuts() {
|
|
// operandOnlyRadioButton: always enabled
|
|
// labelInsteadRadioButton: symbol is unknown and operand address has no label
|
|
// operandAndLabelRadioButton: same as labelInstead
|
|
// operandAndProjRadioButton: symbol is unknown and operand address is outside project
|
|
|
|
string labelStr = symbolTextBox.Text;
|
|
ShortcutArg = -1;
|
|
|
|
// Is this a known symbol? If so, disable most options and bail.
|
|
if (mProject.SymbolTable.TryGetValue(labelStr, out Symbol sym)) {
|
|
labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled =
|
|
operandAndProjButton.IsEnabled = false;
|
|
operandOnlyButton.IsChecked = true;
|
|
return;
|
|
}
|
|
|
|
if (mAttr.OperandOffset >= 0) {
|
|
// Operand target is inside the file. Does the target offset already have a label?
|
|
int targetOffset =
|
|
DataAnalysis.GetBaseOperandOffset(mProject, mAttr.OperandOffset);
|
|
bool hasLabel = mProject.UserLabels.ContainsKey(targetOffset);
|
|
labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled =
|
|
!hasLabel;
|
|
operandAndProjButton.IsEnabled = false;
|
|
ShortcutArg = targetOffset;
|
|
} else if (mAttr.OperandAddress >= 0) {
|
|
// Operand target is outside the file.
|
|
labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled = false;
|
|
operandAndProjButton.IsEnabled = true;
|
|
ShortcutArg = mAttr.OperandAddress;
|
|
} else {
|
|
// Probably an immediate operand.
|
|
// ?? Should operandAndProjButton be enabled for 8-bit constants? We'd want
|
|
// to add it as a constant rather than an address.
|
|
labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled =
|
|
operandAndProjButton.IsEnabled = false;
|
|
}
|
|
|
|
// Select the default option if the currently-selected option is no longer available.
|
|
if ((labelInsteadButton.IsChecked == true && labelInsteadButton.IsEnabled != true) ||
|
|
(operandAndLabelButton.IsChecked == true && !operandAndLabelButton.IsEnabled == true) ||
|
|
(operandAndProjButton.IsChecked == true && !operandAndProjButton.IsEnabled == true)) {
|
|
operandOnlyButton.IsChecked = true;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|