Data Bank Register management, part 2

Changed basic data item from an "extended enum" to a class, so we can
keep track of where things come from (useful for the display list).

Finished edit dialog.  Added serialization to project file.
This commit is contained in:
Andy McFadden 2020-07-09 11:10:39 -07:00
parent 18e6951f17
commit 973d162edb
9 changed files with 273 additions and 83 deletions

View File

@ -58,6 +58,33 @@ namespace Asm65 {
return true;
}
/// <summary>
/// Parses a hexadecimal integer. Leading '$' or "0x" will be ignored.
///
/// Trim whitespace before calling here.
/// </summary>
/// <param name="str">String to parse.</param>
/// <param name="val">Integer value of string.</param>
/// <returns>True if the parsing was successful.</returns>
public static bool TryParseIntHex(string str, out int val) {
if (str.Length == 0) {
val = 0;
return false;
}
if (str[0] == '$') {
str = str.Substring(1);
}
// leading "0x" will be handled by Convert
try {
val = Convert.ToInt32(str, 16);
} catch (Exception) {
val = 0;
return false;
}
return true;
}
/// <summary>
/// Parses a long integer in a variety of formats (hex, decimal, binary). We allow
/// hex to be identified with a leading '$' as well as "0x".

View File

@ -1224,16 +1224,73 @@ namespace SourceGen {
/// <summary>
/// Data Bank Register value.
/// </summary>
/// <remarks>
/// This is primarily a value from $00-ff, but we also want to encode the B=K special
/// mode.
/// </remarks>
public enum DbrValue : short {
// $00-ff is bank number
Unknown = -1, // unknown / do-nothing
ProgramBankReg = -2 // set B=K
public class DbrValue {
public const short UNKNOWN = -1;
public const short USE_PBR = -2;
/// <summary>
/// If true, ignore Bank, use Program Bank Register instead.
/// </summary>
public bool FollowPbr;
/// <summary>
/// Bank number (0-255).
/// </summary>
public byte Bank { get; private set; }
public enum Source { Unknown = 0, User, Auto, Smart };
/// <summary>
/// From whence this value originates.
/// </summary>
public Source ValueSource { get; private set; }
/// <summary>
/// Representation of the object state as a short integer. 0-255 specifies the
/// bank, while negative values are used for special conditions.
/// </summary>
public short AsShort {
get {
if (FollowPbr) {
return USE_PBR;
} else {
return Bank;
}
}
}
public DbrValue(bool followPbr, byte bank, Source source) {
FollowPbr = followPbr;
Bank = bank;
ValueSource = source;
}
public override string ToString() {
return "DBR:" + (FollowPbr ? "K" : "$" + Bank.ToString("x2"));
}
public static bool operator ==(DbrValue a, DbrValue 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.
return a.Bank == b.Bank && a.FollowPbr == b.FollowPbr &&
a.ValueSource == b.ValueSource;
}
public static bool operator !=(DbrValue a, DbrValue b) {
return !(a == b);
}
public override bool Equals(object obj) {
return obj is Symbol && this == (DbrValue)obj;
}
public override int GetHashCode() {
return Bank + (FollowPbr ? 0x100 : 0);
}
}
/// <summary>
/// Determines the value of the Data Bank Register (DBR, register 'B') for relevant
/// instructions, and updates the Anattrib OperandOffset value.
@ -1244,21 +1301,21 @@ namespace SourceGen {
short[] bval = new short[mAnattribs.Length];
// Initialize all entries to "unknown".
Misc.Memset(bval, (short)DbrValue.Unknown);
Misc.Memset(bval, DbrValue.UNKNOWN);
// Set B=K every time we cross an address boundary and the program bank changes.
DbrValue prevBank = DbrValue.Unknown;
short prevBank = DbrValue.UNKNOWN;
foreach (AddressMap.AddressMapEntry ent in mAddrMap) {
int mapBank = ent.Addr >> 16;
if (mapBank != (int)prevBank) {
bval[ent.Offset] = (short)mapBank;
prevBank = (DbrValue)mapBank;
short mapBank = (short)(ent.Addr >> 16);
if (mapBank != prevBank) {
bval[ent.Offset] = mapBank;
prevBank = mapBank;
}
}
// Apply the user-specified values.
foreach (KeyValuePair<int, DbrValue> kvp in userValues) {
bval[kvp.Key] = (short)kvp.Value;
bval[kvp.Key] = kvp.Value.AsShort;
}
// Run through the file, looking for PHK/PLB pairs. When we find one, set an
@ -1270,19 +1327,19 @@ namespace SourceGen {
// Run through file, updating instructions as needed.
short curVal = (short)DbrValue.Unknown;
short curVal = DbrValue.UNKNOWN;
for (int offset = 0; offset < mAnattribs.Length; offset++) {
if (bval[offset] != (short)DbrValue.Unknown) {
if (bval[offset] != DbrValue.UNKNOWN) {
curVal = bval[offset];
}
if (!mAnattribs[offset].UsesDataBankReg) {
continue;
}
Debug.Assert(mAnattribs[offset].IsInstructionStart);
Debug.Assert(curVal != (short)DbrValue.Unknown);
Debug.Assert(curVal != DbrValue.UNKNOWN);
int bank;
if (curVal == (short)DbrValue.ProgramBankReg) {
if (curVal == DbrValue.USE_PBR) {
bank = mAnattribs[offset].Address >> 16;
} else {
Debug.Assert(curVal >= 0 && curVal < 256);

View File

@ -2120,15 +2120,13 @@ namespace SourceGen {
break;
case UndoableChange.ChangeType.SetDataBank: {
// If there's no entry, treat it as an entry with value = Unknown.
if (!DbrValues.TryGetValue(offset, out CodeAnalysis.DbrValue current)) {
current = CodeAnalysis.DbrValue.Unknown;
}
DbrValues.TryGetValue(offset, out CodeAnalysis.DbrValue current);
if (current != (CodeAnalysis.DbrValue)oldValue) {
Debug.WriteLine("GLITCH: old DBR value mismatch (" +
current + " vs " + oldValue + ")");
Debug.Assert(false);
}
if ((CodeAnalysis.DbrValue)newValue == CodeAnalysis.DbrValue.Unknown) {
if (newValue == null) {
DbrValues.Remove(offset);
} else {
DbrValues[offset] = (CodeAnalysis.DbrValue)newValue;

View File

@ -1873,19 +1873,16 @@ namespace SourceGen {
int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
int offset = CodeLineList[selIndex].FileOffset;
CodeAnalysis.DbrValue curValue;
if (!mProject.DbrValues.TryGetValue(offset, out curValue)) {
curValue = CodeAnalysis.DbrValue.Unknown;
}
// Get current user-specified value, or null.
mProject.DbrValues.TryGetValue(offset, out CodeAnalysis.DbrValue curValue);
EditDataBank dlg = new EditDataBank(mMainWin, mProject.AddrMap, mFormatter, curValue);
EditDataBank dlg = new EditDataBank(mMainWin, mProject, mFormatter, curValue);
if (dlg.ShowDialog() != true) {
return;
}
if (dlg.Result != curValue) {
Debug.WriteLine("Changing DBR at +" + offset.ToString("x6") + " to $" +
((int)(dlg.Result)).ToString("x2"));
Debug.WriteLine("Changing DBR at +" + offset.ToString("x6") + " to $" + dlg.Result);
UndoableChange uc =
UndoableChange.CreateDataBankChange(offset, curValue, dlg.Result);
ChangeSet cs = new ChangeSet(uc);

View File

@ -401,6 +401,18 @@ namespace SourceGen {
}
}
}
public class SerDbrValue {
// Skip the ValueSource property; should always be User in project file.
public bool FollowPbr;
public byte Bank;
public SerDbrValue() { }
public SerDbrValue(CodeAnalysis.DbrValue dbrValue) {
FollowPbr = dbrValue.FollowPbr;
Bank = dbrValue.Bank;
Debug.Assert(dbrValue.ValueSource == CodeAnalysis.DbrValue.Source.User);
}
}
// Fields are serialized to/from JSON. DO NOT change the field names.
public int _ContentVersion { get; set; }
@ -420,6 +432,7 @@ namespace SourceGen {
public List<SerVisBitmapAnimation> VisualizationAnimations { get; set; }
public Dictionary<string, SerVisualizationSet> VisualizationSets { get; set; }
public Dictionary<string, DisasmProject.RelocData> RelocList { get; set; }
public Dictionary<string, SerDbrValue> DbrValues { get; set; }
/// <summary>
/// Serializes a DisasmProject into an augmented JSON string.
@ -534,6 +547,13 @@ namespace SourceGen {
spf.RelocList.Add(kvp.Key.ToString(), kvp.Value);
}
// We could output the value as a short, using DbrValue.AsShort, but it doesn't
// save much space and could make life harder down the road.
spf.DbrValues = new Dictionary<string, SerDbrValue>(proj.DbrValues.Count);
foreach (KeyValuePair<int, CodeAnalysis.DbrValue> kvp in proj.DbrValues) {
spf.DbrValues.Add(kvp.Key.ToString(), new SerDbrValue(kvp.Value));
}
JavaScriptSerializer ser = new JavaScriptSerializer();
string cereal = ser.Serialize(spf);
sb.Append(cereal);
@ -854,6 +874,19 @@ namespace SourceGen {
}
}
// Deserialize data bank register values. This was added in v1.7.
if (spf.DbrValues != null) {
foreach (KeyValuePair<string, SerDbrValue> kvp in spf.DbrValues) {
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
Res.Strings.PROJECT_FIELD_DBR_VALUE, report, out int intKey)) {
continue;
}
CodeAnalysis.DbrValue newDbr = new CodeAnalysis.DbrValue(kvp.Value.FollowPbr,
kvp.Value.Bank, CodeAnalysis.DbrValue.Source.User);
proj.DbrValues.Add(intKey, newDbr);
}
}
return true;
}

View File

@ -152,6 +152,7 @@ limitations under the License.
<system:String x:Key="str_ProgressAssembling">Executing assembler...</system:String>
<system:String x:Key="str_ProgressGeneratingFmt">Generating {0}...</system:String>
<system:String x:Key="str_ProjectFieldComment">comment</system:String>
<system:String x:Key="str_ProjectFieldDbrValue">DBR value</system:String>
<system:String x:Key="str_ProjectFieldLongComment">long comment</system:String>
<system:String x:Key="str_ProjectFieldLvTable">local variable table</system:String>
<system:String x:Key="str_ProjectFieldNote">note</system:String>

View File

@ -285,6 +285,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ProgressGeneratingFmt");
public static string PROJECT_FIELD_COMMENT =
(string)Application.Current.FindResource("str_ProjectFieldComment");
public static string PROJECT_FIELD_DBR_VALUE =
(string)Application.Current.FindResource("str_ProjectFieldDbrValue");
public static string PROJECT_FIELD_LONG_COMMENT =
(string)Application.Current.FindResource("str_ProjectFieldLongComment");
public static string PROJECT_FIELD_LV_TABLE =

View File

@ -20,12 +20,17 @@ limitations under the License.
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGen.WpfGui"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="Set Data Bank"
SizeToContent="WidthAndHeight" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
ContentRendered="Window_ContentRendered">
<Window.Resources>
<system:String x:Key="str_OtherBank">(other)</system:String>
</Window.Resources>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -33,25 +38,25 @@ limitations under the License.
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="Enter bank:"/>
<TextBox Name="bankValueBox" Margin="8,1,0,0" Width="24" MaxLength="2"
Text="{Binding DataBankStr, FallbackValue=88}"
<TextBlock Text="Data bank:"/>
<TextBox Name="bankValueBox" Margin="8,1,0,0" Width="30" MaxLength="3"
Text="{Binding DataBankStr, UpdateSourceTrigger=PropertyChanged, FallbackValue=$00}"
FontFamily="{StaticResource GeneralMonoFont}"/>
</StackPanel>
<TextBlock Grid.Row="1" Margin="0,4,0,0" Text="• Enter hex value, or 'K' to track PBR"/>
<TextBlock Grid.Row="1" Margin="0,4,0,0" Text="• Enter two-digit hex value, or 'K' to track PBR"/>
<TextBlock Grid.Row="2" Margin="0,0,0,0" Text="• Leave blank to reset to default"/>
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="0,16,0,0">
<TextBlock Text="Banks in use:" Margin="0,3,0,0"/>
<ComboBox Name="bankCombo" Margin="8,1,0,0" Width="200"
FontFamily="{StaticResource GeneralMonoFont}"
ItemsSource="{Binding BankLabels}" DisplayMemberPath="Label"/>
</StackPanel>
<TextBlock Grid.Row="3" Text="Alternatively, select from list of in-use banks:" Margin="0,16,0,0"/>
<ComboBox Grid.Row="4" Name="bankCombo" Margin="0,4,0,0"
FontFamily="{StaticResource GeneralMonoFont}"
ItemsSource="{Binding BankLabels}" DisplayMemberPath="Label"
SelectionChanged="bankCombo_SelectionChanged"/>
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
<Button Content="OK" IsDefault="True" Width="70"
IsEnabled="{Binding IsValid}" Click="OkButton_Click"/>
<Button Content="Cancel" IsCancel="True" Width="70" Margin="4,0,0,0"/>

View File

@ -19,7 +19,7 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using Asm65;
using CommonUtil;
@ -38,13 +38,14 @@ namespace SourceGen.WpfGui {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private AddressMap mAddrMap;
private DisasmProject mProject;
private Formatter mFormatter;
private bool mSettingComboBox;
private string mDataBankStr;
public string DataBankStr {
get { return mDataBankStr; }
set { mDataBankStr = value; OnPropertyChanged(); }
set { mDataBankStr = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mIsValid;
@ -53,50 +54,64 @@ namespace SourceGen.WpfGui {
set { mIsValid = value; OnPropertyChanged(); }
}
public class BankLabel {
public CodeAnalysis.DbrValue Bank { get; private set; }
public class BankLabelItem : IComparable<BankLabelItem> {
//public CodeAnalysis.DbrValue Bank { get; private set; }
public byte Bank { get; private set; }
public string Label { get; private set; }
public BankLabel(CodeAnalysis.DbrValue bank, string label) {
public BankLabelItem(byte bank, string label) {
Bank = bank;
Label = label;
}
public int CompareTo(BankLabelItem other) {
return Bank - other.Bank;
}
}
public List<BankLabel> BankLabels { get; private set; } = new List<BankLabel>();
public List<BankLabelItem> BankLabels { get; private set; } = new List<BankLabelItem>();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Parent window.</param>
public EditDataBank(Window owner, AddressMap addrMap, Formatter formatter,
/// <param name="proj">Disassembly project.</param>
/// <param name="formatter">Text formatter.</param>
/// <param name="curValue">Current value, or null if none set.</param>
public EditDataBank(Window owner, DisasmProject proj, Formatter formatter,
CodeAnalysis.DbrValue curValue) {
InitializeComponent();
Owner = owner;
DataContext = this;
mAddrMap = addrMap;
mProject = proj;
mFormatter = formatter;
if (curValue == CodeAnalysis.DbrValue.ProgramBankReg) {
DataBankStr = PROG_BANK_STR;
} else if (curValue == CodeAnalysis.DbrValue.Unknown) {
DataBankStr = string.Empty;
} else if ((int)curValue >= 0 && (int)curValue <= 255) {
// Format as address rather than hexvalue so we don't get leading '$'.
DataBankStr = formatter.FormatAddress((int)curValue, false);
} else {
Debug.Assert(false, "invalid DBR value " + curValue);
DataBankStr = string.Empty;
PopulateComboBox();
DataBankStr = DbrValueToString(curValue); // sets combo box
IsValid = true;
}
private void PopulateComboBox() {
// Entry #0 is always the "other" option.
string otherStr = (string)FindResource("str_OtherBank");
BankLabels.Add(new BankLabelItem(0, otherStr));
bool[] done = new bool[256];
foreach (AddressMap.AddressMapEntry ent in mProject.AddrMap) {
byte bank = (byte)(ent.Addr >> 16);
if (done[bank]) {
continue;
}
done[bank] = true;
Anattrib attr = mProject.GetAnattrib(ent.Offset);
string label = (attr.Symbol != null) ? attr.Symbol.Label : string.Empty;
BankLabels.Add(new BankLabelItem(bank,
mFormatter.FormatHexValue(bank, 2) + " " + label));
}
// TODO: combo box
BankLabels.Add(new BankLabel((CodeAnalysis.DbrValue)1, "(other)"));
BankLabels.Add(new BankLabel((CodeAnalysis.DbrValue)1, "$02 FirstBankLabel"));
BankLabels.Add(new BankLabel((CodeAnalysis.DbrValue)1, "$88 FancyBank"));
bankCombo.SelectedIndex = 0;
IsValid = true; // TODO: validate
BankLabels.Sort();
}
private void Window_ContentRendered(object sender, EventArgs e) {
@ -105,31 +120,86 @@ namespace SourceGen.WpfGui {
}
private void OkButton_Click(object sender, RoutedEventArgs e) {
Result = GetValue(DataBankStr);
Result = StringToDbrValue(DataBankStr);
DialogResult = true;
}
private void UpdateControls() {
CodeAnalysis.DbrValue dbrVal = StringToDbrValue(DataBankStr);
IsValid = (string.IsNullOrEmpty(DataBankStr) || dbrVal != null);
SetComboBoxSelection(dbrVal);
}
/// <summary>
/// Sets the selected item in the combo box based on the value in the text edit box.
/// </summary>
private void SetComboBoxSelection(CodeAnalysis.DbrValue dbrVal) {
mSettingComboBox = true; // recursion guard
//CodeAnalysis.DbrValue dbrVal = StringToDbrValue(DataBankStr);
int index = 0;
if (dbrVal != null && !dbrVal.FollowPbr) {
// skip first entry
for (int i = 1; i < BankLabels.Count; i++) {
if (BankLabels[i].Bank == dbrVal.Bank) {
index = i;
break;
}
}
}
bankCombo.SelectedIndex = index;
mSettingComboBox = false;
}
/// <summary>
/// Reacts to a combo box selection change.
/// </summary>
private void bankCombo_SelectionChanged(object sender, SelectionChangedEventArgs e) {
if (mSettingComboBox) {
// Selection changed programatically, don't update text edit box.
return;
}
if (bankCombo.SelectedIndex == 0) {
// "other", don't update text edit box
return;
}
BankLabelItem item = (BankLabelItem)bankCombo.SelectedItem;
DataBankStr = mFormatter.FormatHexValue(item.Bank, 2);
}
/// <summary>
/// Converts a DBR value string to a value.
/// </summary>
/// <param name="valueStr">String to convert.</param>
/// <returns>DBR value.</returns>
private static CodeAnalysis.DbrValue GetValue(string valueStr) {
if (valueStr == PROG_BANK_STR) {
return CodeAnalysis.DbrValue.ProgramBankReg;
/// <returns>DBR value, or null if invalid.</returns>
private static CodeAnalysis.DbrValue StringToDbrValue(string valueStr) {
valueStr = valueStr.Trim();
if (valueStr.Equals(PROG_BANK_STR, StringComparison.InvariantCultureIgnoreCase)) {
return new CodeAnalysis.DbrValue(true, 0, CodeAnalysis.DbrValue.Source.User);
} else {
// Try to parse as 1- or 2-digit hex value.
try {
int val = Convert.ToInt32(valueStr, 16);
if (val < 0 || val > 255) {
// invalid value
return CodeAnalysis.DbrValue.Unknown;
}
return (CodeAnalysis.DbrValue)val;
} catch (Exception ex) {
Debug.WriteLine("Result parse failed: " + ex.Message);
return CodeAnalysis.DbrValue.Unknown;
if (!Number.TryParseIntHex(valueStr, out int val)) {
Debug.WriteLine("Unable to parse '" + valueStr + "' as hex value");
return null;
}
if (val != (byte)val) {
Debug.WriteLine("Val " + val + " out of range of byte");
return null;
}
return new CodeAnalysis.DbrValue(false, (byte)val,
CodeAnalysis.DbrValue.Source.User);
}
}
private string DbrValueToString(CodeAnalysis.DbrValue value) {
if (value == null) {
return string.Empty;
} else if (value.FollowPbr) {
return PROG_BANK_STR;
} else {
return mFormatter.FormatHexValue(value.Bank, 2);
}
}
}