1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-13 14:29:30 +00:00
6502bench/SourceGen/WpfGui/EditInstructionOperand.xaml.cs
Andy McFadden 60b024d24e Improve instruction operand edit edge case
The instruction operand editor has a shortcut button for editing a
project symbol.  Attempting to change the comment field twice
without closing the operand editor in between would result in a
complaint about duplicate symbol names.
2022-01-12 14:17:28 -08:00

1246 lines
52 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 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; }
/// <summary>
/// Offset of label that was edited. A non-negative value here indicates that an
/// edit has been made.
/// </summary>
public int SymbolEditOffsetResult { get; private set; }
/// <summary>
/// Edited project property, or null if no changes were made.
/// </summary>
public DefSymbol ProjectPropertyResult { get; private set; }
/// <summary>
/// The project property that was modified, or null if none.
/// </summary>
public DefSymbol PrevProjectPropertyResult { get; private set; }
/// <summary>
/// Updated label.
/// </summary>
public Symbol SymbolEditResult { 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;
private readonly string LV_MATCH_FOUND_ADDRESS;
private readonly string LV_MATCH_FOUND_CONSTANT;
private readonly string CREATE_LABEL;
private readonly string EDIT_LABEL;
private readonly string CREATE_PROJECT_SYMBOL;
private readonly string EDIT_PROJECT_SYMBOL;
private readonly string CURRENT_LABEL;
private readonly string CURRENT_LABEL_ADJUSTED_FMT;
/// <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");
LV_MATCH_FOUND_ADDRESS = (string)FindResource("str_LvMatchFoundAddress");
LV_MATCH_FOUND_CONSTANT = (string)FindResource("str_LvMatchFoundConstant");
CREATE_LABEL = (string)FindResource("str_CreateLabel");
EDIT_LABEL = (string)FindResource("str_EditLabel");
CREATE_PROJECT_SYMBOL = (string)FindResource("str_CreateProjectSymbol");
EDIT_PROJECT_SYMBOL = (string)FindResource("str_EditProjectSymbol");
CURRENT_LABEL = (string)FindResource("str_CurrentLabel");
CURRENT_LABEL_ADJUSTED_FMT = (string)FindResource("str_CurrentLabelAdjustedFmt");
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();
NumericReferences_Loaded();
LocalVariables_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");
}
}
if (mLabelHasBeenEdited) {
SymbolEditOffsetResult = mEditedLabelOffset;
SymbolEditResult = mEditedLabel;
}
ProjectPropertyResult = mEditedProjectSymbol;
DialogResult = true;
}
/// <summary>
/// Looks up the symbol in the symbol table. If not found there, it checks for a
/// match against the existing or edited project symbol.
/// </summary>
private bool LookupSymbol(string label, bool isNonUnique, out Symbol sym) {
if (isNonUnique) {
// Only applies to labels, so no need to check mEditedProjectSymbol. We
// could check mEditedLabel, but there's no reason to add a symbolic reference
// on top of the numeric reference.
int targetOffset;
Anattrib attr = mProject.GetAnattrib(mOffset);
if (attr.OperandOffset >= 0) {
targetOffset = attr.OperandOffset;
} else {
targetOffset = mOffset;
}
sym = mProject.FindBestNonUniqueLabel(label, targetOffset);
if (sym != null) {
return true;
}
} else {
if (mProject.SymbolTable.TryGetValue(label, out sym)) {
return true;
}
if (mEditedProjectSymbol != null && label.Equals(mEditedProjectSymbol.Label)) {
sym = mEditedProjectSymbol;
return true;
}
}
return false;
}
/// <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;
IsSymbolVar = false;
IsPartPanelEnabled = false;
SymbolValueDecimal = string.Empty;
if (FormatSymbol) {
IsPartPanelEnabled = mOpDef.IsExtendedImmediate;
string trimLabel = Symbol.TrimAndValidateLabel(SymbolLabel,
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
out bool unused2, out bool hasNonUniquePrefix,
out Symbol.LabelAnnotation unused3);
if (!isValid) {
SymbolValueHex = SYMBOL_INVALID;
IsValid = false;
} else if (LookupSymbol(trimLabel, hasNonUniquePrefix, 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.
//
// Referencing an auto label is unwise because we use weak references
// by name, and auto labels can appear, disappear, or be renamed.
IsValid = false;
IsSymbolAuto = true;
} else if (sym.SymbolSource == Symbol.Source.Variable) {
// Local variables can be de-duplicated and uniquified, so referring to
// them by name doesn't make sense. The numeric operand formatter will
// disregard attempts to use them in this way.
IsValid = false;
IsSymbolVar = 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 --
// but it's a little weird for non-unique symbols.
SymbolValueHex = SYMBOL_UNKNOWN;
}
} else {
SymbolValueHex = SYMBOL_NOT_USED;
}
// We want to disable the create/edit label button if a symbol has been
// specified, because the label being edited is the one that the numeric
// reference points to, not the one the symbol points to.
// TODO(maybe): leave it enabled if the symbolic ref matches the numeric ref.
IsDiddleLabelEnabled = !FormatSymbol;
UpdatePreview();
UpdateCopyToOperand();
}
/// <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 ||
mOpDef.AddrMode == OpDef.AddressMode.DPPCRel) {
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:
string trimLabel = Symbol.TrimAndValidateLabel(SymbolLabel,
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
out bool unused2, out bool hasNonUniquePrefix,
out Symbol.LabelAnnotation unused3);
if (string.IsNullOrEmpty(trimLabel)) {
sb.Append("?");
} else if (LookupSymbol(trimLabel, hasNonUniquePrefix, 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 + " (?)");
//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;
// this calls UpdateControls; don't do it twice
} else {
UpdateControls();
}
}
}
private string mSymbolLabel;
public bool IsSymbolAuto {
get { return mIsSymbolAuto; }
set { mIsSymbolAuto = value; OnPropertyChanged(); }
}
private bool mIsSymbolAuto;
public bool IsSymbolVar{
get { return mIsSymbolVar; }
set { mIsSymbolVar = value; OnPropertyChanged(); }
}
private bool mIsSymbolVar;
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 tagged as code-stop. 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 = Symbol.ConvertLabelForDisplay(dfd.SymbolRef.Label,
Symbol.LabelAnnotation.None, true, mFormatter);
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:
case FormatDescriptor.Type.Uninit:
case FormatDescriptor.Type.Junk:
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;
}
// Deal with non-unique labels. If the label refers to an existing
// symbol, use its label, which will have the tag. If the label doesn't
// have a match, discard it -- we don't support weak refs to ambiguous
// non-unique symbols.
string trimLabel = Symbol.TrimAndValidateLabel(SymbolLabel,
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
out bool unused2, out bool hasNonUniquePrefix,
out Symbol.LabelAnnotation unused3);
if (isValid && hasNonUniquePrefix) {
if (LookupSymbol(trimLabel, hasNonUniquePrefix, out Symbol sym)) {
trimLabel = sym.Label;
} else {
Debug.WriteLine("Attempt to create ref to non-existant non-unique sym");
return null;
}
}
return FormatDescriptor.Create(instructionLength,
new WeakSymbolRef(trimLabel, 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 ShowNarNotAddress {
get { return mShowNarNotAddress; }
set { mShowNarNotAddress = value; OnPropertyChanged(); }
}
private bool mShowNarNotAddress;
public bool ShowNarEditLabel {
get { return mShowNarEditLabel; }
set { mShowNarEditLabel = value; OnPropertyChanged(); }
}
private bool mShowNarEditLabel;
public bool ShowNarCurrentLabel {
get { return mShowNarCurrentLabel; }
set { mShowNarCurrentLabel = value; OnPropertyChanged(); }
}
private bool mShowNarCurrentLabel;
public string NarLabelOffsetText {
get { return mNarLabelOffsetText; }
set { mNarLabelOffsetText = value; OnPropertyChanged(); }
}
private string mNarLabelOffsetText;
public string NarTargetLabel {
get { return mNarTargetLabel; }
set { mNarTargetLabel = value; OnPropertyChanged(); }
}
private string mNarTargetLabel;
public string CreateEditLabelText {
get { return mCreateEditLabelText; }
set { mCreateEditLabelText = value; OnPropertyChanged(); }
}
private string mCreateEditLabelText;
public bool ShowNarExternalSymbol {
get { return mShowNarExternalSymbol; }
set { mShowNarExternalSymbol = value; OnPropertyChanged(); }
}
private bool mShowNarExternalSymbol;
public bool ShowNarPlatformSymbol {
get { return mShowNarPlatformSymbol; }
set { mShowNarPlatformSymbol = value; OnPropertyChanged(); }
}
private bool mShowNarPlatformSymbol;
public string NarPlatformSymbol {
get { return mNarPlatformSymbol; }
set { mNarPlatformSymbol = value; OnPropertyChanged(); }
}
private string mNarPlatformSymbol;
public bool ShowNarNoProjectMatch {
get { return mShowNarNoProjectMatch; }
set { mShowNarNoProjectMatch = value; OnPropertyChanged(); }
}
private bool mShowNarNoProjectMatch;
public bool ShowNarProjectSymbol {
get { return mShowNarProjectSymbol; }
set { mShowNarProjectSymbol = value; OnPropertyChanged(); }
}
private bool mShowNarProjectSymbol;
public string NarProjectSymbol {
get { return mNarProjectSymbol; }
set { mNarProjectSymbol = value; OnPropertyChanged(); }
}
private string mNarProjectSymbol;
public string CreateEditProjectSymbolText {
get { return mCreateEditProjectSymbolText; }
set { mCreateEditProjectSymbolText = value; OnPropertyChanged(); }
}
private string mCreateEditProjectSymbolText;
public bool IsCopyToOperandEnabled {
get { return mIsCopyToOperandEnabled; }
set { mIsCopyToOperandEnabled = value; OnPropertyChanged(); }
}
private bool mIsCopyToOperandEnabled;
public bool IsDiddleLabelEnabled {
get { return mIsDiddleLabelEnabled; }
set { mIsDiddleLabelEnabled = value; OnPropertyChanged(); }
}
private bool mIsDiddleLabelEnabled;
/// <summary>
/// Edited label value. Will be null if the label hasn't been created, or has been
/// deleted (by entering a blank string in the label edit box).
/// </summary>
private Symbol mEditedLabel;
/// <summary>
/// Set to true if the label has been edited.
/// </summary>
private bool mLabelHasBeenEdited;
/// <summary>
/// Address associated with the label (for the Symbol's value).
/// </summary>
private int mLabelTargetAddress = -1;
/// <summary>
/// Offset of edited label.
/// </summary>
private int mEditedLabelOffset = -1;
/// <summary>
/// Edited project symbol. If a symbol already exists, this will be initialized to the
/// existing value. Otherwise this will be null.
/// </summary>
/// <remarks>
/// Project symbols can't be deleted from here, so a null reference always means that
/// there's no symbol and we haven't made an edit.
/// </remarks>
private DefSymbol mEditedProjectSymbol;
/// <summary>
/// Configures the UI in the Numeric Address Reference box at load time.
/// </summary>
private void NumericReferences_Loaded() {
SymbolEditOffsetResult = -1;
Anattrib attr = mProject.GetAnattrib(mOffset);
if (attr.OperandOffset >= 0) {
// Operand target is inside the file.
ShowNarEditLabel = true;
// Seek back to the start of the instruction or data item if the operand points
// into the middle of one. This is *not* the same as the "nearby" search,
// which will traverse multiple items to find a match.
// TODO: this can create a situation where the code list shows FUBAR-1 but we
// edit an earlier label, if the earlier label has a multi-byte format that
// includes the target address. (An example can be found in 20200-ui-edge-cases.)
mEditedLabelOffset =
DataAnalysis.GetBaseOperandOffset(mProject, attr.OperandOffset);
mLabelTargetAddress = mProject.GetAnattrib(mEditedLabelOffset).Address;
if (mProject.UserLabels.TryGetValue(mEditedLabelOffset, out Symbol sym)) {
// Has a label.
ShowNarCurrentLabel = true;
if (mEditedLabelOffset != attr.OperandOffset) {
NarLabelOffsetText = string.Format(CURRENT_LABEL_ADJUSTED_FMT,
mFormatter.FormatAdjustment(attr.OperandOffset - mEditedLabelOffset));
} else {
NarLabelOffsetText = CURRENT_LABEL;
}
NarTargetLabel = sym.GenerateDisplayLabel(mFormatter);
mEditedLabel = sym;
CreateEditLabelText = EDIT_LABEL;
} else {
NarLabelOffsetText = CURRENT_LABEL;
CreateEditLabelText = CREATE_LABEL;
}
} else if (attr.OperandAddress >= 0) {
ShowNarExternalSymbol = true;
// There can be multiple symbols with the same value, so we walk through the
// list and identify the first matching platform and project symbols. We're
// only interested in address symbols, not constants.
Symbol firstPlatform = null;
Symbol firstProject = null;
foreach (Symbol sym in mProject.SymbolTable) {
if (sym.Value == attr.OperandAddress && !sym.IsConstant) {
if (firstPlatform == null && sym.SymbolSource == Symbol.Source.Platform) {
firstPlatform = sym;
} else if (firstProject == null &&
sym.SymbolSource == Symbol.Source.Project) {
firstProject = sym;
}
if (firstPlatform != null && firstProject != null) {
break;
}
}
}
if (firstPlatform != null) {
ShowNarPlatformSymbol = true;
NarPlatformSymbol = firstPlatform.Label;
}
if (firstProject != null) {
ShowNarProjectSymbol = true;
NarProjectSymbol = firstProject.Label;
CreateEditProjectSymbolText = EDIT_PROJECT_SYMBOL;
mEditedProjectSymbol = (DefSymbol)firstProject;
PrevProjectPropertyResult = mEditedProjectSymbol;
} else {
ShowNarNoProjectMatch = true;
CreateEditProjectSymbolText = CREATE_PROJECT_SYMBOL;
}
} else {
// Probably an immediate operand.
ShowNarNotAddress = true;
}
}
private void UpdateCopyToOperand() {
IsCopyToOperandEnabled = false;
if (mEditedProjectSymbol != null) {
// We have a pre-existing or recently-edited symbol. See if the current
// operand configuration already matches.
if (!FormatSymbol || !mEditedProjectSymbol.Label.Equals(SymbolLabel)) {
IsCopyToOperandEnabled = true;
}
}
}
private void EditLabel_Click(object sender, RoutedEventArgs e) {
EditLabel dlg = new EditLabel(this, mEditedLabel, mLabelTargetAddress,
mEditedLabelOffset, mProject.SymbolTable, mFormatter);
if (dlg.ShowDialog() != true || mEditedLabel == dlg.LabelSym) {
Debug.WriteLine("No change to label, ignoring edit");
return;
}
mEditedLabel = dlg.LabelSym;
mLabelHasBeenEdited = true;
// Update UI to match current state.
if (mEditedLabel == null) {
ShowNarCurrentLabel = false;
CreateEditLabelText = CREATE_LABEL;
} else {
ShowNarCurrentLabel = true;
CreateEditLabelText = EDIT_LABEL;
NarTargetLabel = mEditedLabel.GenerateDisplayLabel(mFormatter);
}
// Sort of nice to just hit return twice after entering a label, so move the focus
// to the OK button.
okButton.Focus();
}
private void EditProjectSymbol_Click(object sender, RoutedEventArgs e) {
DefSymbol origSym = mEditedProjectSymbol;
if (origSym == null) {
// Need to start with a symbol so we can set the value field.
string symName = "SYM";
if (!string.IsNullOrEmpty(SymbolLabel) &&
Asm65.Label.ValidateLabel(SymbolLabel)) {
symName = SymbolLabel;
}
origSym = new DefSymbol(symName, mOperandValue, Symbol.Source.Project,
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None);
}
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,
mProject.ProjectProps.ProjectSyms, origSym, null, false, true);
if (dlg.ShowDialog() != true) {
return;
}
Debug.Assert(dlg.NewSym != null); // can't delete a symbol from dialog
if (mEditedProjectSymbol == dlg.NewSym) {
Debug.WriteLine("No change to project symbol, ignoring edit");
return;
}
mEditedProjectSymbol = dlg.NewSym;
ShowNarProjectSymbol = true;
ShowNarNoProjectMatch = false;
NarProjectSymbol = mEditedProjectSymbol.Label;
CreateEditProjectSymbolText = EDIT_PROJECT_SYMBOL;
// The preview and symbol value display will use mEditedProjectSymbol if it's the
// only place the symbol exists, so we want to keep the other controls updated.
UpdateControls();
// Move the focus to the OK button.
okButton.Focus();
}
private void CopyToOperandButton_Click(object sender, RoutedEventArgs e) {
FormatSymbol = true;
SymbolLabel = mEditedProjectSymbol.Label;
IsCopyToOperandEnabled = false;
// changes to controls will call UpdateControls() for us
}
#endregion Numeric References
#region Local Variables
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 LvMatchFoundText {
get { return mLvMatchFoundText; }
set { mLvMatchFoundText = value; OnPropertyChanged(); }
}
private string mLvMatchFoundText;
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 = -1;
/// <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 in the Local Variable box at load time.
/// </summary>
private void LocalVariables_Loaded() {
if (!mOpDef.IsDirectPageInstruction && !mOpDef.IsStackRelInstruction) {
ShowLvNotApplicable = true;
return;
}
LvMatchFoundText = mOpDef.IsDirectPageInstruction ?
LV_MATCH_FOUND_ADDRESS : LV_MATCH_FOUND_CONSTANT;
LocalVariableLookup lvLookup =
new LocalVariableLookup(mProject.LvTables, mProject, null, false, 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;
DefSymbol existingVar = lvLookup.GetSymbol(mOffset, mOperandValue,
mOpDef.IsDirectPageInstruction ?
Symbol.Type.ExternalAddr : Symbol.Type.Constant);
if (existingVar == null) {
ShowLvNoMatchFound = true;
CreateEditLocalVariableText = CREATE_LOCAL_VARIABLE;
} else {
mEditedLocalVar = lvLookup.GetOriginalForm(existingVar);
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);
}
}
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, Symbol.LabelAnnotation.None,
FormatDescriptor.SubType.None, 1, true, string.Empty,
DefSymbol.DirectionFlags.ReadWrite, null, string.Empty);
}
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;
ShowLvNoMatchFound = false;
ShowLvMatchFound = true;
} else {
Debug.WriteLine("No change to def symbol, ignoring edit");
}
okButton.Focus();
}
}
#endregion Local Variables
}
}