diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs
index db22390..76a93d4 100644
--- a/SourceGen/DataAnalysis.cs
+++ b/SourceGen/DataAnalysis.cs
@@ -718,6 +718,7 @@ namespace SourceGen {
#else
// Select "is printable" test. We use the extended version to include some
// control characters.
+ // TODO(maybe): require some *actually* printable characters in each string
CharEncoding.InclusionTest testPrintable;
FormatDescriptor.SubType baseSubType;
switch (mAnalysisParams.DefaultTextScanMode) {
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index 8c2e969..eab74b5 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -1845,6 +1845,7 @@ namespace SourceGen {
}
private void EditInstructionOperand(int offset) {
+#if false
EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, offset,
mProject, mOutputFormatter);
@@ -1912,7 +1913,21 @@ namespace SourceGen {
case WpfGui.EditInstructionOperand.SymbolShortcutAction.None:
break;
}
+#else
+ EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, mProject,
+ offset, mOutputFormatter);
+ if (dlg.ShowDialog() != true) {
+ return;
+ }
+ ChangeSet cs = new ChangeSet(1);
+ mProject.OperandFormats.TryGetValue(offset, out FormatDescriptor dfd);
+ if (dlg.FormatDescriptorResult != dfd) {
+ UndoableChange uc = UndoableChange.CreateOperandFormatChange(offset,
+ dfd, dlg.FormatDescriptorResult);
+ cs.Add(uc);
+ }
+#endif
if (cs.Count != 0) {
ApplyUndoableChanges(cs);
}
diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs
index 0b9f917..2edd3f7 100644
--- a/SourceGen/PseudoOp.cs
+++ b/SourceGen/PseudoOp.cs
@@ -836,7 +836,9 @@ namespace SourceGen {
}
operandValue = (int)(operandValue & mask);
- if (sb.Length != symLabel.Length) {
+ // If we've added stuff, and we're going to add an adjustment later, stick
+ // an extra space in between for readability.
+ if (sb.Length != symLabel.Length && operandValue != symbolValue) {
sb.Append(' ');
}
} else {
diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj
index 38e2e67..a060190 100644
--- a/SourceGen/SourceGen.csproj
+++ b/SourceGen/SourceGen.csproj
@@ -109,9 +109,6 @@
EditInstructionOperand.xaml
-
- EditInstructionOperand2.xaml
- EditLabel.xaml
@@ -269,10 +266,6 @@
DesignerMSBuild:Compile
-
- Designer
- MSBuild:Compile
- DesignerMSBuild:Compile
diff --git a/SourceGen/WpfGui/EditInstructionOperand.xaml b/SourceGen/WpfGui/EditInstructionOperand.xaml
index c4c01e6..e16d592 100644
--- a/SourceGen/WpfGui/EditInstructionOperand.xaml
+++ b/SourceGen/WpfGui/EditInstructionOperand.xaml
@@ -19,6 +19,7 @@ limitations under the License.
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Edit Instruction Operand"
@@ -26,72 +27,144 @@ limitations under the License.
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded"
ContentRendered="Window_ContentRendered">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+ N/A
+ [invalid symbol name]
+ ?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
diff --git a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs
index 99b6295..96f3e67 100644
--- a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs
+++ b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs
@@ -26,78 +26,16 @@ using Asm65;
namespace SourceGen.WpfGui {
///
/// Instruction operand editor.
- ///
- /// This is a pretty direct port from WinForms.
///
- public partial class EditInstructionOperand : Window {
+ public partial class EditInstructionOperand : Window, INotifyPropertyChanged {
///
- /// In/out. May be null on entry if the offset doesn't have a format descriptor
- /// specified. Will be null on exit if "default" is selected.
+ /// Updated format descriptor. Will be null if the user selected "default".
///
- public FormatDescriptor FormatDescriptor { get; set; }
+ public FormatDescriptor FormatDescriptorResult { get; private set; }
- public enum SymbolShortcutAction {
- None = 0, CreateLabelInstead, CreateLabelAlso, CreateProjectSymbolAlso
- }
-
- ///
- /// Remember the last option we used.
- ///
- private static SymbolShortcutAction sLastAction = SymbolShortcutAction.None;
-
- ///
- /// On OK dialog exit, specifies that an additional action should be taken.
- ///
- public SymbolShortcutAction ShortcutAction { get; private set; }
-
- ///
- /// Additional argument, meaning dependent on ShortcutAction. This will either be
- /// the target label offset or the project symbol value.
- ///
- public int ShortcutArg { get; private set; }
-
- ///
- /// Width of full instruction, including opcode.
- ///
- private int mInstructionLength;
-
- ///
- /// Number of hexadecimal digits to show in the preview. Sometimes you want
- /// to force this to be longer or shorter than InstructionLength would indicate,
- /// e.g. "BRA $1000" has a 1-byte operand.
- ///
- private int mPreviewHexDigits;
-
- ///
- /// Operand value, extracted from file data. For a relative branch, this will be
- /// an address instead. Only used for preview window.
- ///
- private int mOperandValue;
-
- ///
- /// Is the operand an immediate value? If so, we enable the symbol part selection.
- ///
- private bool mIsExtendedImmediate;
-
- ///
- /// Is the operand a PC relative offset?
- ///
- private bool mIsPcRelative;
-
- ///
- /// Special handling for block move instructions (MVN/MVP).
- ///
- private bool mIsBlockMove;
-
- ///
- /// If set, show a '#' in the preview indow.
- ///
- private bool mShowHashPrefix;
-
- /////
- ///// Symbol table to use when resolving symbolic values.
- /////
- //private SymbolTable SymbolTable { get; set; }
+ private readonly string SYMBOL_NOT_USED;
+ private readonly string SYMBOL_UNKNOWN;
+ private readonly string SYMBOL_INVALID;
///
/// Project reference.
@@ -105,305 +43,564 @@ namespace SourceGen.WpfGui {
private DisasmProject mProject;
///
- /// Formatter to use when displaying addresses and hex values.
+ /// Offset of instruction being edited.
+ ///
+ private int mOffset;
+
+ ///
+ /// Format object.
///
private Formatter mFormatter;
///
- /// Copy of operand Anattribs.
+ /// Operation definition, from file data.
///
- private Anattrib mAttr;
+ private OpDef mOpDef;
///
- /// Set this during initial control configuration, so we know to ignore the CheckedChanged
- /// events.
+ /// Status flags at the point where the instruction is defined. This tells us whether
+ /// an operand is 8-bit or 16-bit.
///
- private bool mIsInitialSetup;
+ private StatusFlags mOpStatusFlags;
///
- /// Set to true if the user has entered a symbol that matches an auto-generated symbol.
+ /// Operand value, extracted from file data. For a relative branch, this will be
+ /// an address instead.
///
- private bool mIsSymbolAuto;
+ private int mOperandValue;
+
+ ///
+ /// True when the input is valid. Controls whether the OK button is enabled.
+ ///
+ public bool IsValid {
+ get { return mIsValid; }
+ set { mIsValid = value; OnPropertyChanged(); }
+ }
+ private bool mIsValid;
+
+ ///
+ /// Set when our load-time initialization is complete.
+ ///
+ private bool mLoadDone;
+
+ // INotifyPropertyChanged implementation
+ public event PropertyChangedEventHandler PropertyChanged;
+ private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
- public EditInstructionOperand(Window owner, int offset, DisasmProject project,
- Asm65.Formatter formatter) {
+ ///
+ /// Constructor.
+ ///
+ /// Parent window.
+ /// Project reference.
+ /// File offset of instruction start.
+ /// Formatter object, for preview window.
+ public EditInstructionOperand(Window owner, DisasmProject project, int offset,
+ Formatter formatter) {
InitializeComponent();
Owner = owner;
DataContext = this;
mProject = project;
+ mOffset = offset;
mFormatter = formatter;
- // Configure the appearance.
- mAttr = mProject.GetAnattrib(offset);
- OpDef op = mProject.CpuDef.GetOpDef(mProject.FileData[offset]);
- mInstructionLength = mAttr.Length;
- mPreviewHexDigits = (mAttr.Length - 1) * 2;
- if (mAttr.OperandAddress >= 0) {
+ SYMBOL_NOT_USED = (string)FindResource("str_SymbolNotUsed");
+ SYMBOL_INVALID = (string)FindResource("str_SymbolNotValid");
+ SYMBOL_UNKNOWN = (string)FindResource("str_SymbolUnknown");
+
+ 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 = mAttr.OperandAddress;
-
- if (op.AddrMode == OpDef.AddressMode.PCRel) {
- mPreviewHexDigits = 4;
- mIsPcRelative = true;
- } else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
- op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
- mIsPcRelative = true;
- }
+ mOperandValue = attr.OperandAddress;
} else {
- int opVal = op.GetOperand(mProject.FileData, offset, mAttr.StatusFlags);
- mOperandValue = opVal;
- if (op.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.
- mIsBlockMove = true;
- mOperandValue = opVal >> 8;
- mPreviewHexDigits = 2;
- }
+ // For BlockMove this will have both parts.
+ mOperandValue = mOpDef.GetOperand(project.FileData, offset, attr.StatusFlags);
}
- mIsExtendedImmediate = op.IsExtendedImmediate; // Imm, PEA, MVN/MVP
- mShowHashPrefix = op.IsImmediate; // just Imm
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
- mIsInitialSetup = true;
-
- // 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) {
- asciiButton.IsEnabled =
- CharEncoding.IsPrintableLowOrHighAscii((byte)mOperandValue);
- petsciiButton.IsEnabled =
- CharEncoding.IsPrintableC64Petscii((byte)mOperandValue);
- screenCodeButton.IsEnabled =
- CharEncoding.IsPrintableC64ScreenCode((byte)mOperandValue);
- } else {
- asciiButton.IsEnabled = petsciiButton.IsEnabled = screenCodeButton.IsEnabled =
- false;
- }
-
- // Configure the dialog from the FormatDescriptor, if one is available.
- SetControlsFromDescriptor(FormatDescriptor);
-
- // Do this whether or not symbol is checked -- want to have this set when the
- // dialog is initially in default format.
- switch (sLastAction) {
- case SymbolShortcutAction.CreateLabelInstead:
- labelInsteadButton.IsChecked = true;
- break;
- case SymbolShortcutAction.CreateLabelAlso:
- operandAndLabelButton.IsChecked = true;
- break;
- case SymbolShortcutAction.CreateProjectSymbolAlso:
- operandAndProjButton.IsChecked = true;
- break;
- default:
- operandOnlyButton.IsChecked = true;
- break;
- }
-
- mIsInitialSetup = false;
- UpdateControls();
+ BasicFormat_Loaded();
+ mLoadDone = true;
}
-
private void Window_ContentRendered(object sender, EventArgs e) {
- // Start with the focus in the text box. This way they can start typing
- // immediately.
+ UpdateControls();
+ symbolTextBox.SelectAll();
symbolTextBox.Focus();
}
-
- private void SymbolTextBox_TextChanged(object sender, TextChangedEventArgs e) {
- // Make sure Symbol is checked if they're typing text in.
- symbolButton.IsChecked = true;
- UpdateControls();
- }
-
- ///
- /// Handles Checked/Unchecked events for all radio buttons in main group.
- ///
- private void MainGroup_CheckedChanged(object sender, RoutedEventArgs e) {
- // Enable/disable the low/high/bank radio group.
- // Update preview window.
- UpdateControls();
- }
-
- ///
- /// Handles Checked/Unchecked events for all radio buttons in symbol-part group.
- ///
- private void PartGroup_CheckedChanged(object sender, RoutedEventArgs e) {
- // Update preview window.
- UpdateControls();
- }
-
private void OkButton_Click(object sender, RoutedEventArgs e) {
- FormatDescriptor = CreateDescriptorFromControls();
-
- //
- // Extract the current shortcut action. For dialog configuration purposes we
- // want to capture the current state. For the caller, we force it to "none"
- // if we're not using a symbol format.
- //
- SymbolShortcutAction action = SymbolShortcutAction.None;
- if (labelInsteadButton.IsChecked == true) {
- action = SymbolShortcutAction.CreateLabelInstead;
- } else if (operandAndLabelButton.IsChecked == true) {
- action = SymbolShortcutAction.CreateLabelAlso;
- } else if (operandAndProjButton.IsChecked == true) {
- action = SymbolShortcutAction.CreateProjectSymbolAlso;
- } else if (operandOnlyButton.IsChecked == true) {
- action = SymbolShortcutAction.None;
- } else {
- Debug.Assert(false);
- action = SymbolShortcutAction.None;
- }
- sLastAction = action;
-
- if (symbolButton.IsChecked == true && FormatDescriptor != null) {
- // Only report a shortcut action if they've entered a symbol. If they
- // checked symbol but left the field blank, they're just trying to delete
- // the format.
- ShortcutAction = action;
- } else {
- ShortcutAction = SymbolShortcutAction.None;
- }
-
+ FormatDescriptorResult = CreateDescriptorFromControls();
DialogResult = true;
}
///
- /// Updates all of the controls to reflect the current internal state.
+ /// Updates the state of the UI controls as the user interacts with the dialog.
///
private void UpdateControls() {
- if (mIsInitialSetup) {
+ if (!mLoadDone) {
return;
}
- symbolPartPanel.IsEnabled = (symbolButton.IsChecked == true && mIsExtendedImmediate);
- symbolShortcutsGroupBox.IsEnabled = symbolButton.IsChecked == true;
- SetPreviewText();
+ // Parts panel IsEnabled depends directly on formatSymbolButton.IsChecked.
+ IsValid = true;
+ IsSymbolAuto = false;
+ SymbolValueDecimal = string.Empty;
+ if (FormatSymbol) {
+ 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;
+ }
- bool isOk = true;
- if (symbolButton.IsChecked == true) {
- // Just check for correct format. References to non-existent labels are allowed.
- //
- // We try to block references to auto labels, but it's possible to get around it
- // (replace auto label with user label, reference non-existent auto label,
- // remove user label). We could try harder, but currently not necessary.
- isOk = !mIsSymbolAuto && Asm65.Label.ValidateLabel(symbolTextBox.Text);
-
- // Allow empty strings as a way to delete the label and return to "default".
- if (string.IsNullOrEmpty(symbolTextBox.Text)) {
- isOk = 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;
}
-
- ConfigureSymbolShortcuts();
+ } else {
+ SymbolValueHex = SYMBOL_NOT_USED;
}
- okButton.IsEnabled = isOk;
+
+ UpdatePreview();
}
///
- /// Sets the text displayed in the "preview" text box.
+ /// Updates the contents of the preview text box.
///
- private void SetPreviewText() {
- //symbolValueLabel.Text = string.Empty;
- mIsSymbolAuto = false;
-
+ 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) {
- // Default format. We can't actually know what this look like, so just
- // clear the box.
- previewTextBox.Text = string.Empty;
+ // 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;
}
- if (dfd.FormatSubType == FormatDescriptor.SubType.Symbol &&
- string.IsNullOrEmpty(dfd.SymbolRef.Label)) {
- // no label yet, nothing to show
- previewTextBox.Text = 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('#');
}
- StringBuilder preview = new StringBuilder();
- if (mShowHashPrefix) {
- preview.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:
- preview.Append(mFormatter.FormatHexValue(mOperandValue, mPreviewHexDigits));
+ sb.Append(mFormatter.FormatHexValue(operandValue, previewHexDigits));
break;
case FormatDescriptor.SubType.Decimal:
- preview.Append(mFormatter.FormatDecimalValue(mOperandValue));
+ sb.Append(mFormatter.FormatDecimalValue(operandValue));
break;
case FormatDescriptor.SubType.Binary:
- preview.Append(mFormatter.FormatBinaryValue(mOperandValue, 8));
+ 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);
- preview.Append(mFormatter.FormatCharacterValue(mOperandValue, enc));
+ sb.Append(mFormatter.FormatCharacterValue(operandValue, enc));
break;
case FormatDescriptor.SubType.Symbol:
if (mProject.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) {
- if (mIsBlockMove) {
- // For a 24-bit symbol, we grab the high byte. This is the
- // expected behavior, according to Eyes & Lichty; see the
- // explanation of the MVP instruction. For an 8-bit symbol
- // the assembler just takes the value.
- // TODO(someday): allow a different symbol for each part of the
- // operand.
- if (sym.Value > 0xff) {
- bankButton.IsChecked = true;
- } else {
- lowButton.IsChecked = true;
- }
- dfd = CreateDescriptorFromControls();
- }
+ // 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 "