1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-29 10:50:28 +00:00

Add Data Bank Register management, part 1

On the 65816, 16-bit data access instructions (e.g. LDA abs) are
expanded to 24 bits by merging in the Data Bank Register (B).  The
value of the register is difficult to determine via static analysis,
so we need a way to annotate the disassembly with the correct value.
Without this, the mapping of address to file offset will sometimes
be incorrect.

This change adds the basic data structures and "fixup" function, a
functional but incomplete editor, and source for a new test case.
This commit is contained in:
Andy McFadden 2020-07-08 17:56:27 -07:00
parent 44522dc2f2
commit 18e6951f17
15 changed files with 590 additions and 22 deletions

View File

@ -74,5 +74,35 @@ namespace CommonUtil {
Debug.WriteLine("Crashed while crashing");
}
}
/// <summary>
/// Clears an array to a specific value, similar to memset() in libc. This is much
/// faster than setting array elements individually.
/// </summary>
/// <remarks>
/// From https://stackoverflow.com/a/18659408/294248
///
/// Invokes Array.Copy() on overlapping elements. Other approaches involve using
/// Buffer.BlockCopy or unsafe code. Apparently .NET Core has an Array.Fill(), but
/// that doesn't exist in .NET Framework.
///
/// We could get off the line a little faster by setting the first 16 or so elements in
/// a loop, bailing out if we finish early, so we don't start calling Array.Copy() until
/// it's actually faster to do so. I don't expect to be calling this often or for
/// small arrays though.
/// </remarks>
/// <typeparam name="T">Array element type.</typeparam>
/// <param name="array">Array reference.</param>
/// <param name="elem">Initialization value.</param>
public static void Memset<T>(T[] array, T elem) {
//Array.Fill(array, elem);
int length = array.Length;
if (length == 0) return;
array[0] = elem;
int count;
for (count = 1; count <= length / 2; count *= 2)
Array.Copy(array, 0, array, count, count);
Array.Copy(array, 0, array, count, length - count);
}
}
}

View File

@ -36,6 +36,8 @@ namespace SourceGen {
InlineData = 1 << 2, // byte is inline data
Data = 1 << 3, // byte is data
UsesDataBankReg = 1 << 4, // operand value should be merged with DBR
EntryPoint = 1 << 8, // external code branches here
BranchTarget = 1 << 9, // internal code branches here
ExternalBranch = 1 << 10, // this abs/rel branch lands outside input file
@ -113,6 +115,18 @@ namespace SourceGen {
return IsInstructionStart || IsDataStart || IsInlineDataStart;
}
}
public bool UsesDataBankReg {
get {
return (mAttribFlags & AttribFlags.UsesDataBankReg) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.UsesDataBankReg;
} else {
mAttribFlags &= ~AttribFlags.UsesDataBankReg;
}
}
}
public bool IsEntryPoint {
get {
return (mAttribFlags & AttribFlags.EntryPoint) != 0;

View File

@ -838,14 +838,17 @@ namespace SourceGen {
/// The operand's address, and if applicable, the operand's file offset, are
/// stored in the Anattrib array.
///
/// Doesn't do anything with immediate data.
/// </summary>
/// <remarks>
/// For PC-relative operands (e.g. branches) it's tempting to simply adjust the file
/// offset by the specified amount and convert that to an address. If the file
/// has multiple ORGs, this can produce incorrect results. We need to convert the
/// opcode's offset to an address, adjust by the operand, and then find the file
/// offset that corresponds to the target address.
///
/// Doesn't do anything with immediate data.
/// </summary>
/// This is called once per instruction, on the analyzer's first visit.
/// </remarks>
/// <param name="offset">Offset of the instruction opcode.</param>
/// <param name="op">Opcode being handled. (Passed in because the caller has it
/// handy.)</param>
@ -854,26 +857,36 @@ namespace SourceGen {
int operand = op.GetOperand(mFileData, offset, mAnattribs[offset].StatusFlags);
// Add the bank to get a 24-bit address. We're currently using the program bank
// (K) rather than the data bank (B), which is correct for absolute and relative
// branches but wrong for 16-bit data operations. We currently have no way to
// know what the value of B is, so we use K because there's some small chance
// of it being correct.
// TODO(someday): figure out how to get the correct value for the B reg
// Add the bank to get a 24-bit address. For some instructions the relevant bank
// is known, because the operand is merged with the Program Bank Register (K) or
// is always in bank 0. For some we need the Data Bank Register (B).
//
// Instead of trying to track the B register during code analysis, we mark the
// relevant instructions now and fix them up later. We can get away with this
// because the DBR is only applied to data-load instructions, which don't affect
// the flow of the analysis pass. The value of B *is* affected by the analysis
// pass because a "smart PLB" handler needs to know where all the code is, so it's
// more efficient to figure it out later.
int bank = mAnattribs[offset].Address & 0x7fff0000;
// Extract target address.
switch (op.AddrMode) {
// These might refer to a location in the file, or might be external.
case OpDef.AddressMode.Abs:
case OpDef.AddressMode.AbsIndexX:
case OpDef.AddressMode.AbsIndexY:
case OpDef.AddressMode.AbsIndexXInd:
case OpDef.AddressMode.StackAbs:
case OpDef.AddressMode.Abs: // uses DBR iff !IsAbsolutePBR
case OpDef.AddressMode.AbsIndexX: // uses DBR
case OpDef.AddressMode.AbsIndexY: // uses DBR
if (!op.IsAbsolutePBR) {
mAnattribs[offset].UsesDataBankReg = true;
}
// Merge the PBR even if we eventually want the DBR; less to fix later.
mAnattribs[offset].OperandAddress = operand | bank;
break;
case OpDef.AddressMode.AbsInd: // JMP (addr), always bank 0
case OpDef.AddressMode.AbsIndLong: // JMP [addr], always bank 0
case OpDef.AddressMode.StackAbs: // assume PBR
case OpDef.AddressMode.AbsIndexXInd: // JMP (addr,X); uses program bank
mAnattribs[offset].OperandAddress = operand | bank;
break;
case OpDef.AddressMode.AbsInd: // JMP (addr); always bank 0
case OpDef.AddressMode.AbsIndLong: // JMP [addr]; always bank 0
case OpDef.AddressMode.DP:
case OpDef.AddressMode.DPIndexX:
case OpDef.AddressMode.DPIndexY:
@ -888,7 +901,7 @@ namespace SourceGen {
break;
case OpDef.AddressMode.AbsIndexXLong:
case OpDef.AddressMode.AbsLong:
// 24-bit address, don't add bank
// 24-bit address, don't alter bank
mAnattribs[offset].OperandAddress = operand;
break;
case OpDef.AddressMode.PCRel: // rel operand; convert to absolute addr
@ -1207,5 +1220,88 @@ namespace SourceGen {
throw new PluginException("Instr format rej: unknown sub type " + pluginSubType);
}
}
/// <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
}
/// <summary>
/// Determines the value of the Data Bank Register (DBR, register 'B') for relevant
/// instructions, and updates the Anattrib OperandOffset value.
/// </summary>
public void ApplyDataBankRegister(Dictionary<int, DbrValue> userValues) {
Debug.Assert(!mCpuDef.HasAddr16); // 65816 only
short[] bval = new short[mAnattribs.Length];
// Initialize all entries to "unknown".
Misc.Memset(bval, (short)DbrValue.Unknown);
// Set B=K every time we cross an address boundary and the program bank changes.
DbrValue 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;
}
}
// Apply the user-specified values.
foreach (KeyValuePair<int, DbrValue> kvp in userValues) {
bval[kvp.Key] = (short)kvp.Value;
}
// Run through the file, looking for PHK/PLB pairs. When we find one, set an
// entry for the PLB instruction unless an entry already exists there.
// ? look for LDA #imm8 / PHA / PLB?
// ...
// Run through file, updating instructions as needed.
short curVal = (short)DbrValue.Unknown;
for (int offset = 0; offset < mAnattribs.Length; offset++) {
if (bval[offset] != (short)DbrValue.Unknown) {
curVal = bval[offset];
}
if (!mAnattribs[offset].UsesDataBankReg) {
continue;
}
Debug.Assert(mAnattribs[offset].IsInstructionStart);
Debug.Assert(curVal != (short)DbrValue.Unknown);
int bank;
if (curVal == (short)DbrValue.ProgramBankReg) {
bank = mAnattribs[offset].Address >> 16;
} else {
Debug.Assert(curVal >= 0 && curVal < 256);
bank = curVal << 16;
}
int newAddr = (mAnattribs[offset].OperandAddress & 0x0000ffff) | bank;
int newOffset = mAddrMap.AddressToOffset(offset, newAddr);
if (newAddr != mAnattribs[offset].OperandAddress ||
newOffset != mAnattribs[offset].OperandOffset) {
Debug.WriteLine("DBR rewrite at +" + offset.ToString("x6") + ": $" +
mAnattribs[offset].OperandAddress.ToString("x6") + "/+" +
mAnattribs[offset].OperandOffset.ToString("x6") + " --> $" +
newAddr.ToString("x6") + "/+" + newOffset.ToString("x6"));
mAnattribs[offset].OperandAddress = newAddr;
mAnattribs[offset].OperandOffset = newOffset;
}
}
}
}
}

View File

@ -74,6 +74,11 @@ namespace SourceGen {
/// </summary>
public string[] Comments { get; private set; }
/// <summary>
/// Data Bank Register overrides.
/// </summary>
public Dictionary<int, CodeAnalysis.DbrValue> DbrValues { get; private set; }
/// <summary>
/// Full line, possibly multi-line comments.
/// </summary>
@ -274,6 +279,7 @@ namespace SourceGen {
Comments[i] = string.Empty;
}
DbrValues = new Dictionary<int, CodeAnalysis.DbrValue>();
LongComments = new Dictionary<int, MultiLineComment>();
Notes = new SortedList<int, MultiLineComment>();
@ -807,6 +813,13 @@ namespace SourceGen {
ca.Analyze();
reanalysisTimer.EndTask("CodeAnalysis.Analyze");
if (!CpuDef.HasAddr16) {
// 24-bit address space, so DBR matters
reanalysisTimer.StartTask("CodeAnalysis.ApplyDataBankRegister");
ca.ApplyDataBankRegister(DbrValues);
reanalysisTimer.EndTask("CodeAnalysis.ApplyDataBankRegister");
}
// Save a copy of the current state.
mCodeOnlyAnattribs = new Anattrib[mAnattribs.Length];
Array.Copy(mAnattribs, mCodeOnlyAnattribs, mAnattribs.Length);
@ -831,6 +844,8 @@ namespace SourceGen {
DataAnalysis da = new DataAnalysis(this, mAnattribs);
da.DebugLog = debugLog;
// Convert references to addresses into references to labels, generating labels
// as needed.
reanalysisTimer.StartTask("DataAnalysis.AnalyzeDataTargets");
da.AnalyzeDataTargets();
reanalysisTimer.EndTask("DataAnalysis.AnalyzeDataTargets");
@ -2103,6 +2118,27 @@ namespace SourceGen {
UndoableChange.ReanalysisScope.CodeAndData);
}
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;
}
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) {
DbrValues.Remove(offset);
} else {
DbrValues[offset] = (CodeAnalysis.DbrValue)newValue;
}
// ignore affectedOffsets
Debug.Assert(uc.ReanalysisRequired ==
UndoableChange.ReanalysisScope.CodeAndData);
}
break;
case UndoableChange.ChangeType.SetTypeHint: {
// Always requires full code+data re-analysis.
ApplyTypeHints((TypedRangeSet)oldValue, (TypedRangeSet)newValue);

View File

@ -1858,6 +1858,41 @@ namespace SourceGen {
EditLongComment(LineListGen.Line.HEADER_COMMENT_OFFSET);
}
public bool CanEditDataBank() {
if (mProject.CpuDef.HasAddr16) {
return false; // only available for 65816
}
if (SelectionAnalysis.mNumItemsSelected != 1) {
return false;
}
return (SelectionAnalysis.mLineType == LineListGen.Line.Type.Code /*||
is data bank */);
}
public void EditDataBank() {
int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
int offset = CodeLineList[selIndex].FileOffset;
CodeAnalysis.DbrValue curValue;
if (!mProject.DbrValues.TryGetValue(offset, out curValue)) {
curValue = CodeAnalysis.DbrValue.Unknown;
}
EditDataBank dlg = new EditDataBank(mMainWin, mProject.AddrMap, 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"));
UndoableChange uc =
UndoableChange.CreateDataBankChange(offset, curValue, dlg.Result);
ChangeSet cs = new ChangeSet(uc);
ApplyUndoableChanges(cs);
}
}
public bool CanEditLabel() {
if (SelectionAnalysis.mNumItemsSelected != 1) {
return false;
@ -4095,7 +4130,7 @@ namespace SourceGen {
}
public void ShowFileHexDump() {
if (!OpenAnyFile(out string pathName)) {
if (!OpenAnyFile(null, out string pathName)) {
return;
}
FileInfo fi = new FileInfo(pathName);
@ -4134,7 +4169,7 @@ namespace SourceGen {
}
public void SliceFiles() {
if (!OpenAnyFile(out string pathName)) {
if (!OpenAnyFile(null, out string pathName)) {
return;
}
@ -4144,7 +4179,7 @@ namespace SourceGen {
}
public void ConvertOmf() {
if (!OpenAnyFile(out string pathName)) {
if (!OpenAnyFile(Res.Strings.OMF_SELECT_FILE, out string pathName)) {
return;
}
@ -4183,11 +4218,14 @@ namespace SourceGen {
ov.ShowDialog();
}
private bool OpenAnyFile(out string pathName) {
private bool OpenAnyFile(string title, out string pathName) {
OpenFileDialog fileDlg = new OpenFileDialog() {
Filter = Res.Strings.FILE_FILTER_ALL,
FilterIndex = 1
};
if (title != null) {
fileDlg.Title = title;
}
if (fileDlg.ShowDialog() != true) {
pathName = null;
return false;
@ -4285,7 +4323,7 @@ namespace SourceGen {
}
public void Debug_ApplesoftToHtml() {
if (!OpenAnyFile(out string basPathName)) {
if (!OpenAnyFile(null, out string basPathName)) {
return;
}

View File

@ -133,6 +133,7 @@ limitations under the License.
<system:String x:Key="str_OmfSegCommentFmt">Segment {0:D2}: Kind={1}; Attrs={2}; Name='{3}'</system:String>
<system:String x:Key="str_OmfSegHdrCommentFmt">Segment {0:D2}: {3} {1,-9} Name='{2}', Length={4}</system:String>
<system:String x:Key="str_OmfSegNoteFmt">Seg{0:D2}: {1} '{2}'</system:String>
<system:String x:Key="str_OmfSelectFile">Select OMF file</system:String>
<system:String x:Key="str_OpenDataDoesntExist">The file doesn't exist.</system:String>
<system:String x:Key="str_OpenDataEmpty">File is empty</system:String>
<system:String x:Key="str_OpenDataFailCaption">Unable to load data file</system:String>

View File

@ -247,6 +247,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_OmfSegHdrCommentFmt");
public static string OMF_SEG_NOTE_FMT =
(string)Application.Current.FindResource("str_OmfSegNoteFmt");
public static string OMF_SELECT_FILE =
(string)Application.Current.FindResource("str_OmfSelectFile");
public static string OPEN_DATA_DOESNT_EXIST =
(string)Application.Current.FindResource("str_OpenDataDoesntExist");
public static string OPEN_DATA_EMPTY =

View File

@ -339,6 +339,9 @@ not help you debug 6502 projects.</p>
stackoverflow.com links.)</li>
<li>Applesoft to HTML. An experimental feature that formats an
Applesoft program as HTML.</li>
<li>Apply Edit Commands. An experimental feature for importing values,
such as end-of-line comments, that have been generated by other
programs.</li>
<li>Apply Platform Symbols. An experimental feature for turning platform
symbols into address labels. This will run through the list of all
symbols loaded from .sym65 files and find addresses that fall within

View File

@ -0,0 +1,106 @@
; Copyright 2020 faddenSoft. All Rights Reserved.
; See the LICENSE.txt file for distribution terms (Apache 2.0).
;
; Assembler: Merlin 32
org $1000
start
clc
xce
sep #$30
mx %11
lda start
lda bank2
phk
plb
lda start
lda bank2
lda #$02
pha
plb ;EDIT: set DBR=$02
lda start
lda bank2
lda #$08
pha
plb ;EDIT: set DBR=$08
jsl bank2
lda #$02
pha
plb ;EDIT: set DBR=$02
jsl bank3
phk
plb
bit start
jml alldone
code2bz
dw snippet ;EDIT: format as 16-bit address
snippet rts
code2zero
adr code2b ;EDIT: format as 24-bit address
org $022000
bank2
ldal bank2 ;EDIT: set DBR=$08
ldx #$00
jsr code2a
jsr :call2bz
jsr :call2zero
nop
lda #$03
pha
plb ;EDIT: set DBR=$03
lda bank3dat
rtl
:call2bz
jmp (code2bz) ;source/target in bank 0
:call2zero
jmp [code2zero] ;source in bank 0
code2a
jmp (bank2addr,x) ;both addresses formed with K
code2b
nop
rts
bank2addr
dw code2b
org $033000
bank3
ldal bank3 ;EDIT: set DBR=$02
jsr :post_plb
beq :skip_mayhem
; Test PHK/PLB with an address shift in the middle. Not really something
; that should ever happen, but we need to handle everything.
phk
org $033020 ;EDIT: set address
:post_plb
plb
:self nop
bra :skip_mayhem
:skip_mayhem
lda bank2addr
rtl
bank3dat
dw bank3dat
alldone rts

View File

@ -130,6 +130,9 @@
<Compile Include="WpfGui\EditComment.xaml.cs">
<DependentUpon>EditComment.xaml</DependentUpon>
</Compile>
<Compile Include="WpfGui\EditDataBank.xaml.cs">
<DependentUpon>EditDataBank.xaml</DependentUpon>
</Compile>
<Compile Include="WpfGui\EditDataOperand.xaml.cs">
<DependentUpon>EditDataOperand.xaml</DependentUpon>
</Compile>
@ -331,6 +334,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WpfGui\EditDataBank.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WpfGui\EditDataOperand.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -79,6 +79,9 @@ namespace SourceGen {
// Adds, updates, or removes an AddressMap entry.
SetAddress,
// Adds, updates, or removes a data bank register value.
SetDataBank,
// Changes the type hint.
SetTypeHint,
@ -199,6 +202,30 @@ namespace SourceGen {
return uc;
}
/// <summary>
/// Creates an UndoableChange for a data bank register update.
/// </summary>
/// <param name="offset"></param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
/// <returns></returns>
public static UndoableChange CreateDataBankChange(int offset,
CodeAnalysis.DbrValue oldValue, CodeAnalysis.DbrValue newValue) {
if (oldValue == newValue) {
Debug.WriteLine("No-op DBR change at +" + offset.ToString("x6") + ": " + oldValue);
}
UndoableChange uc = new UndoableChange();
uc.Type = ChangeType.SetDataBank;
uc.Offset = offset;
uc.OldValue = oldValue;
uc.NewValue = newValue;
// We don't strictly need to re-analyze the code, since the current implementation
// handles it as a post-analysis fixup, but this lets us avoid having to compute the
// affected offsets.
uc.ReanalysisRequired = ReanalysisScope.CodeAndData;
return uc;
}
/// <summary>
/// Creates an UndoableChange for a type hint update. Rather than adding a
/// separate UndoableChange for each affected offset -- which could span the

View File

@ -0,0 +1,60 @@
<!--
Copyright 2020 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.
-->
<Window x:Class="SourceGen.WpfGui.EditDataBank"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Set Data Bank"
SizeToContent="WidthAndHeight" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
ContentRendered="Window_ContentRendered">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<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}"
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="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>
<StackPanel Grid.Row="4" 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"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,136 @@
/*
* Copyright 2020 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using Asm65;
using CommonUtil;
namespace SourceGen.WpfGui {
/// <summary>
/// Edit Data Bank dialog.
/// </summary>
public partial class EditDataBank : Window, INotifyPropertyChanged {
private const string PROG_BANK_STR = "K";
public CodeAnalysis.DbrValue Result { get; private set; }
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private AddressMap mAddrMap;
private Formatter mFormatter;
private string mDataBankStr;
public string DataBankStr {
get { return mDataBankStr; }
set { mDataBankStr = value; OnPropertyChanged(); }
}
private bool mIsValid;
public bool IsValid {
get { return mIsValid; }
set { mIsValid = value; OnPropertyChanged(); }
}
public class BankLabel {
public CodeAnalysis.DbrValue Bank { get; private set; }
public string Label { get; private set; }
public BankLabel(CodeAnalysis.DbrValue bank, string label) {
Bank = bank;
Label = label;
}
}
public List<BankLabel> BankLabels { get; private set; } = new List<BankLabel>();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Parent window.</param>
public EditDataBank(Window owner, AddressMap addrMap, Formatter formatter,
CodeAnalysis.DbrValue curValue) {
InitializeComponent();
Owner = owner;
DataContext = this;
mAddrMap = addrMap;
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;
}
// 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
}
private void Window_ContentRendered(object sender, EventArgs e) {
bankValueBox.SelectAll();
bankValueBox.Focus();
}
private void OkButton_Click(object sender, RoutedEventArgs e) {
Result = GetValue(DataBankStr);
DialogResult = true;
}
/// <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;
} 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;
}
}
}
}
}

View File

@ -79,6 +79,7 @@ limitations under the License.
<KeyGesture>Ctrl+Semicolon</KeyGesture>
</RoutedUICommand.InputGestures>
</RoutedUICommand>
<RoutedUICommand x:Key="EditDataBankCmd" Text="Edit Data Bank..."/>
<RoutedUICommand x:Key="EditHeaderCommentCmd" Text="Edit Header Comment..."/>
<RoutedUICommand x:Key="EditOperandCmd" Text="Edit Operand...">
<RoutedUICommand.InputGestures>
@ -236,6 +237,8 @@ limitations under the License.
Executed="EditAppSettingsCmd_Executed"/>
<CommandBinding Command="{StaticResource EditCommentCmd}"
CanExecute="CanEditComment" Executed="EditCommentCmd_Executed"/>
<CommandBinding Command="{StaticResource EditDataBankCmd}"
CanExecute="CanEditDataBank" Executed="EditDataBankCmd_Executed"/>
<CommandBinding Command="{StaticResource EditHeaderCommentCmd}"
CanExecute="IsProjectOpen" Executed="EditHeaderCommentCmd_Executed"/>
<CommandBinding Command="{StaticResource EditLabelCmd}"
@ -402,6 +405,7 @@ limitations under the License.
<MenuItem Command="{StaticResource EditOperandCmd}"/>
<MenuItem Command="{StaticResource EditCommentCmd}" InputGestureText="Ctrl+;"/>
<MenuItem Command="{StaticResource EditLongCommentCmd}"/>
<MenuItem Command="{StaticResource EditDataBankCmd}"/>
<MenuItem Command="{StaticResource EditNoteCmd}"/>
<MenuItem Command="{StaticResource EditProjectSymbolCmd}"/>
<MenuItem Command="{StaticResource CreateLocalVariableTableCmd}"/>

View File

@ -1016,6 +1016,10 @@ namespace SourceGen.WpfGui {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditComment();
}
private void CanEditDataBank(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditDataBank();
}
private void CanEditLabel(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditLabel();
}
@ -1170,6 +1174,10 @@ namespace SourceGen.WpfGui {
mMainCtrl.EditComment();
}
private void EditDataBankCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditDataBank();
}
private void EditHeaderCommentCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditHeaderComment();
}