mirror of
https://github.com/fadden/6502bench.git
synced 2024-12-01 22:50:35 +00:00
4322a0c231
We currently have two options for assembly code output, selected by a checkbox in the application settings: always put labels on the same lines as the instruction or data operand, or split the labels onto their own line if they were wider than the label text field. This change adds a third option, which puts labels on their own line whenever possible. Assemblers don't generally allow this for variable assignment pseudo-ops like "foo = $1000", but it's accepted for most other situations. This is a cosmetic change to the output, and will not affect the generated code. The old true/false app setting will be disregarded. "Split if too long" will be used by default. Added test 20280-label-placement to exercise the "split whenever allowed" behavior. The "export" function has a similar option that has not been updated (for no particular reason other than laziness). Also, simplified the app settings GetEnum / SetEnum calls, which can infer the enumerated type from the arguments. This should not impact behavior.
301 lines
12 KiB
C#
301 lines
12 KiB
C#
/*
|
|
* 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.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
|
|
using Asm65;
|
|
|
|
namespace SourceGen.Tools.WpfGui {
|
|
/// <summary>
|
|
/// Apple II text/graphics memory map chart.
|
|
/// </summary>
|
|
public partial class Apple2ScreenChart : Window {
|
|
private const int NUM_HI_RES_ROWS = 192;
|
|
private const int NUM_TEXT_ROWS = 24;
|
|
private const int NUM_TEXT_HOLES = 8;
|
|
private Formatter mFormatter;
|
|
|
|
private static int[] sHiResRowsByAddr = GenerateHiResRowsByAddr();
|
|
private static int[] sTextRowsByAddr = GenerateTextRowsByAddr();
|
|
|
|
public enum ChartMode {
|
|
Unknown = 0,
|
|
HiRes1_L,
|
|
HiRes2_L,
|
|
HiRes1_A,
|
|
HiRes2_A,
|
|
TextWithHoles
|
|
};
|
|
public class ChartModeItem {
|
|
public string Name { get; private set; }
|
|
public ChartMode Mode { get; private set; }
|
|
|
|
public ChartModeItem(string name, ChartMode mode) {
|
|
Name = name;
|
|
Mode = mode;
|
|
}
|
|
}
|
|
public ChartModeItem[] ChartModeItems { get; private set; }
|
|
|
|
|
|
public Apple2ScreenChart(Window owner, Formatter formatter) {
|
|
InitializeComponent();
|
|
Owner = owner;
|
|
DataContext = this;
|
|
|
|
mFormatter = formatter;
|
|
|
|
ChartModeItems = new ChartModeItem[] {
|
|
new ChartModeItem((string)FindResource("str_HiRes1_L"), ChartMode.HiRes1_L),
|
|
new ChartModeItem((string)FindResource("str_HiRes2_L"), ChartMode.HiRes2_L),
|
|
new ChartModeItem((string)FindResource("str_HiRes1_A"), ChartMode.HiRes1_A),
|
|
new ChartModeItem((string)FindResource("str_HiRes2_A"), ChartMode.HiRes2_A),
|
|
new ChartModeItem((string)FindResource("str_TextWithHoles"), ChartMode.TextWithHoles),
|
|
};
|
|
}
|
|
|
|
public void Window_Loaded(object sender, RoutedEventArgs e) {
|
|
// Restore chart mode setting.
|
|
ChartMode mode = AppSettings.Global.GetEnum(AppSettings.A2SC_MODE, ChartMode.HiRes1_L);
|
|
int index = 0;
|
|
for (int i = 0; i < ChartModeItems.Length; i++) {
|
|
if (ChartModeItems[i].Mode == mode) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
chartModeComboBox.SelectedIndex = index;
|
|
// should call UpdateControls via SelectionChanged
|
|
}
|
|
|
|
// Catch ESC key.
|
|
private void Window_KeyEventHandler(object sender, KeyEventArgs e) {
|
|
if (e.Key == Key.Escape) {
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void ChartModeComboBox_SelectionChanged(object sender,
|
|
SelectionChangedEventArgs e) {
|
|
UpdateControls();
|
|
}
|
|
|
|
private void UpdateControls() {
|
|
ChartModeItem item = (ChartModeItem)chartModeComboBox.SelectedItem;
|
|
if (item == null) {
|
|
// initializing
|
|
return;
|
|
}
|
|
|
|
AppSettings.Global.SetEnum(AppSettings.A2SC_MODE, item.Mode);
|
|
|
|
string text;
|
|
switch (item.Mode) {
|
|
case ChartMode.HiRes1_L:
|
|
text = DrawHiRes(0x2000, true);
|
|
break;
|
|
case ChartMode.HiRes2_L:
|
|
text = DrawHiRes(0x4000, true);
|
|
break;
|
|
case ChartMode.HiRes1_A:
|
|
text = DrawHiRes(0x2000, false);
|
|
break;
|
|
case ChartMode.HiRes2_A:
|
|
text = DrawHiRes(0x4000, false);
|
|
break;
|
|
case ChartMode.TextWithHoles:
|
|
text = DrawText();
|
|
break;
|
|
default:
|
|
text = "UNKNOWN MODE";
|
|
break;
|
|
}
|
|
|
|
chartTextBox.Text = text;
|
|
chartTextBox.SelectionStart = text.Length;
|
|
chartTextBox.SelectionLength = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws chart for hi-res graphics.
|
|
/// </summary>
|
|
/// <param name="baseAddr">Base address ($2000/$4000).</param>
|
|
/// <param name="byLine">True if we want to sort by line number, false for address.</param>
|
|
/// <returns>String with entire chart.</returns>
|
|
private string DrawHiRes(int baseAddr, bool byLine) {
|
|
const string eol = " \r\n"; // add space for balance
|
|
const string div = " | ";
|
|
string hdr;
|
|
if (byLine) {
|
|
hdr = "Line Addr ";
|
|
} else {
|
|
hdr = "Addr Line";
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder(
|
|
(hdr.Length * 4 + div.Length * 3 + eol.Length) * (NUM_HI_RES_ROWS / 4));
|
|
|
|
sb.Append(hdr);
|
|
sb.Append(div);
|
|
sb.Append(hdr);
|
|
sb.Append(div);
|
|
sb.Append(hdr);
|
|
sb.Append(div);
|
|
sb.Append(hdr);
|
|
for (int i = 0; i < NUM_HI_RES_ROWS / 4; i++) {
|
|
sb.Append(eol);
|
|
|
|
DrawHiResEntry(baseAddr, byLine, i, sb);
|
|
sb.Append(div);
|
|
DrawHiResEntry(baseAddr, byLine, i + NUM_HI_RES_ROWS / 4, sb);
|
|
sb.Append(div);
|
|
DrawHiResEntry(baseAddr, byLine, i + (NUM_HI_RES_ROWS * 2) / 4, sb);
|
|
sb.Append(div);
|
|
DrawHiResEntry(baseAddr, byLine, i + (NUM_HI_RES_ROWS * 3) / 4, sb);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
private void DrawHiResEntry(int baseAddr, bool byLine, int index, StringBuilder sb) {
|
|
if (byLine) {
|
|
sb.AppendFormat("{0,3:D} {1}", index,
|
|
mFormatter.FormatHexValue(HiResRowToAddr(baseAddr, index), 4));
|
|
} else {
|
|
int row = sHiResRowsByAddr[index];
|
|
sb.AppendFormat("{1} {0,3:D}", row,
|
|
mFormatter.FormatHexValue(HiResRowToAddr(baseAddr, row), 4));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the address of a line on the hi-res screen.
|
|
/// </summary>
|
|
/// <param name="baseAddr">Base address ($2000 or $4000).</param>
|
|
/// <param name="row">Row number, 0-191.</param>
|
|
/// <returns>Address of start of line.</returns>
|
|
private static int HiResRowToAddr(int baseAddr, int row) {
|
|
// If row is ABCDEFGH, we want pppFGHCD EABAB000 (where p would be $20/$40).
|
|
int low = ((row & 0xc0) >> 1) | ((row & 0xc0) >> 3) | ((row & 0x08) << 4);
|
|
int high = ((row & 0x07) << 2) | ((row & 0x30) >> 4);
|
|
int rowAddr = baseAddr + ((high << 8) | low);
|
|
return rowAddr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a sorted list of hi-res row numbers. The ordering is determined by the
|
|
/// address in memory of the row.
|
|
/// </summary>
|
|
/// <returns>List of rows, in memory order.</returns>
|
|
private static int[] GenerateHiResRowsByAddr() {
|
|
SortedList<int, int> addrList = new SortedList<int, int>(NUM_HI_RES_ROWS);
|
|
for (int i = 0; i < NUM_HI_RES_ROWS; i++) {
|
|
addrList.Add(HiResRowToAddr(0, i), i);
|
|
}
|
|
|
|
return addrList.Values.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws chart for the text screen. There are few enough rows that we can do
|
|
/// by-line and by-address for both pages in a reasonable amount of space.
|
|
/// </summary>
|
|
/// <returns>String with entire chart.</returns>
|
|
private string DrawText() {
|
|
const string eol = " \r\n"; // add space for balance
|
|
const string div = " | ";
|
|
const string hdr1 = "Line Page1 Page2";
|
|
const string hdr2 = "Page1 Page2 Line";
|
|
|
|
StringBuilder sb = new StringBuilder(
|
|
(hdr1.Length * 2 + div.Length * 1 + eol.Length) * (NUM_TEXT_ROWS + NUM_TEXT_HOLES));
|
|
|
|
sb.Append(" By Line " + div + " By Address" + eol);
|
|
sb.Append(hdr1);
|
|
sb.Append(div);
|
|
sb.Append(hdr2);
|
|
for (int i = 0; i < NUM_TEXT_ROWS + NUM_TEXT_HOLES; i++) {
|
|
const int base1 = 0x400;
|
|
const int base2 = 0x800;
|
|
|
|
sb.Append(eol);
|
|
|
|
int rowIndex = i < NUM_TEXT_ROWS ? i : NUM_TEXT_ROWS - i - 1;
|
|
int textRow = sTextRowsByAddr[i];
|
|
|
|
if (rowIndex >= 0) {
|
|
sb.AppendFormat(" {0,2:D} {1} {2}", rowIndex,
|
|
mFormatter.FormatHexValue(TextRowToAddr(base1, rowIndex), 4),
|
|
mFormatter.FormatHexValue(TextRowToAddr(base2, rowIndex), 4));
|
|
} else {
|
|
sb.AppendFormat(" H{0} {1} {2}", -rowIndex - 1,
|
|
mFormatter.FormatHexValue(TextRowToAddr(base1, rowIndex), 4),
|
|
mFormatter.FormatHexValue(TextRowToAddr(base2, rowIndex), 4));
|
|
}
|
|
sb.Append(div);
|
|
if (textRow >= 0) {
|
|
sb.AppendFormat("{1} {2} {0,2:D}", textRow,
|
|
mFormatter.FormatHexValue(TextRowToAddr(base1, textRow), 4),
|
|
mFormatter.FormatHexValue(TextRowToAddr(base2, textRow), 4));
|
|
} else {
|
|
sb.AppendFormat("{1} {2} H{0}", -textRow - 1,
|
|
mFormatter.FormatHexValue(TextRowToAddr(base1, textRow), 4),
|
|
mFormatter.FormatHexValue(TextRowToAddr(base2, textRow), 4));
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the address of a line on the text screen.
|
|
/// </summary>
|
|
/// <param name="baseAddr">Base address (0x0400 or 0x0800).</param>
|
|
/// <param name="row">Row number (0-23), or screen hole (-1 - -8).</param>
|
|
/// <returns>Address of start of line.</returns>
|
|
private static int TextRowToAddr(int baseAddr, int row) {
|
|
if (row < 0) {
|
|
// Screen hole: $478, $4f8, ...
|
|
return baseAddr + (-row) * 128 - 8;
|
|
} else {
|
|
// If row is 000ABCDE, we want 0000ppCD EABAB000 (where p is $04/$08).
|
|
int high = (row & 0x06) >> 1;
|
|
int low = (row & 0x18) | ((row & 0x18) << 2) | ((row & 0x01) << 7);
|
|
int rowAddr = baseAddr + ((high << 8) | low);
|
|
return rowAddr;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a sorted list of text row numbers. The ordering is determined by the
|
|
/// address in memory of the row.
|
|
/// </summary>
|
|
/// <returns>List of rows, in memory order.</returns>
|
|
private static int[] GenerateTextRowsByAddr() {
|
|
SortedList<int, int> addrList = new SortedList<int, int>(NUM_TEXT_ROWS);
|
|
for (int i = -NUM_TEXT_HOLES - 1; i < NUM_TEXT_ROWS; i++) {
|
|
addrList.Add(TextRowToAddr(0, i), i);
|
|
}
|
|
|
|
return addrList.Values.ToArray();
|
|
}
|
|
}
|
|
}
|