2019-05-11 17:16:54 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
using Asm65;
|
2019-10-07 00:32:20 +00:00
|
|
|
|
using CommonUtil;
|
2019-07-20 20:28:10 +00:00
|
|
|
|
using FormattedParts = SourceGen.DisplayList.FormattedParts;
|
2019-10-23 20:00:46 +00:00
|
|
|
|
using TextScanMode = SourceGen.ProjectProperties.AnalysisParameters.TextScanMode;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-07-20 20:28:10 +00:00
|
|
|
|
namespace SourceGen {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts file data and Anattrib contents into a series of strings and format metadata.
|
|
|
|
|
/// </summary>
|
2019-05-28 01:46:09 +00:00
|
|
|
|
public class LineListGen {
|
2019-10-13 00:23:32 +00:00
|
|
|
|
/// <summary>
|
2019-11-23 04:45:57 +00:00
|
|
|
|
/// Color multiplier for Notes. Used for "dark" mode.
|
2019-10-13 00:23:32 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public float NoteColorMultiplier { get; set; } = 1.0f;
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// List of display lines.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private List<Line> mLineList;
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// List of formatted parts to be presented to the user. This has one entry per line.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Separating FormattedParts out of Line seems odd at first, but we need changes to
|
|
|
|
|
/// DisplayList to cause events in XAML. I'm thinking the artificial separation of
|
|
|
|
|
/// Line from the formatted data holder may make future ports easier.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private DisplayList mDisplayList;
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Project that contains the data we're formatting, notably the FileData and
|
|
|
|
|
/// Anattribs arrays.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private DisasmProject mProject;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Code/data formatter.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Formatter mFormatter;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If set, prepend cycle counts to EOL comments.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool mShowCycleCounts;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Names for pseudo-ops.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private PseudoOp.PseudoOpNames mPseudoOpNames;
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cache of previously-formatted data. The data is stored with references to
|
|
|
|
|
/// dependencies, so it should not be necessary to explicitly clear this.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private FormattedOperandCache mFormattedLineCache;
|
|
|
|
|
|
2019-08-31 01:33:05 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Local variable table data extractor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private LocalVariableLookup mLvLookup;
|
|
|
|
|
|
2019-10-23 20:00:46 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Character test, for display of character data next to default data items.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private CharEncoding.InclusionTest mCharTest;
|
|
|
|
|
private CharEncoding.Convert mCharConv;
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// One of these per line of output in the display. It should be possible to draw
|
|
|
|
|
/// all of the output without needing to refer back to the project data. (Currently
|
|
|
|
|
/// making an exception for some selection-dependent field highlighting.)
|
|
|
|
|
///
|
|
|
|
|
/// Base fields are immutable, but the Parts property is set after creation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class Line {
|
|
|
|
|
// Extremely-negative offset value ensures it's at the very top.
|
|
|
|
|
public const int HEADER_COMMENT_OFFSET = int.MinValue + 1;
|
|
|
|
|
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
// These need to be bit flags so we can record which parts associated with a
|
|
|
|
|
// given offset are selected.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
[FlagsAttribute]
|
|
|
|
|
public enum Type {
|
|
|
|
|
Unclassified = 0,
|
|
|
|
|
|
|
|
|
|
// Primary functional items.
|
|
|
|
|
Code = 1 << 0,
|
|
|
|
|
Data = 1 << 1, // includes inline data
|
|
|
|
|
CodeOrData = (Code | Data),
|
|
|
|
|
|
|
|
|
|
// Decorative items, added by user or formatter.
|
|
|
|
|
LongComment = 1 << 2,
|
|
|
|
|
Note = 1 << 3,
|
|
|
|
|
Blank = 1 << 4,
|
|
|
|
|
|
|
|
|
|
// Assembler directives.
|
|
|
|
|
OrgDirective = 1 << 5,
|
|
|
|
|
EquDirective = 1 << 6,
|
|
|
|
|
RegWidthDirective = 1 << 7,
|
2020-07-09 22:17:47 +00:00
|
|
|
|
DataBankDirective = 1 << 8,
|
2019-08-26 23:58:53 +00:00
|
|
|
|
|
|
|
|
|
// Additional metadata.
|
2020-07-09 22:17:47 +00:00
|
|
|
|
LocalVariableTable = 1 << 9,
|
|
|
|
|
VisualizationSet = 1 << 10,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Line type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Type LineType { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Numeric offset value. Used to map a line item to the Anattrib. Note this is
|
|
|
|
|
/// set for all lines, and is the same for all lines in a multi-line sequence,
|
|
|
|
|
/// e.g. every line in a long comment has the file offset with which it is associated.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int FileOffset { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Number of offsets this line covers. Will be > 0 for code and data, zero for
|
|
|
|
|
/// everything else. The same value is used for all lines in a multi-line sequence.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int OffsetSpan { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// For multi-line entries, this indicates which line is represented. For
|
|
|
|
|
/// single-line entries, this will be zero.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int SubLineIndex { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Strings for display. Creation may be deferred. Use the DisplayList
|
|
|
|
|
/// GetFormattedParts() method to access this property.
|
|
|
|
|
/// </summary>
|
2019-05-28 01:46:09 +00:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Certain elements, such as multi-line comments, must be formatted to determine
|
|
|
|
|
/// the number of lines they span. We retain the results to avoid formatting
|
|
|
|
|
/// them twice.
|
|
|
|
|
/// </remarks>
|
2019-05-11 17:16:54 +00:00
|
|
|
|
public FormattedParts Parts { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
/// Background color, used for Notes.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public Color BackgroundColor { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// String for searching. May be created on demand when the Line is first searched.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string SearchString { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Line(int offset, int span, Type type) : this(offset, span, type, 0) { }
|
|
|
|
|
|
|
|
|
|
public Line(int offset, int span, Type type, int subLineIndex) {
|
|
|
|
|
FileOffset = offset;
|
|
|
|
|
OffsetSpan = span;
|
|
|
|
|
LineType = type;
|
|
|
|
|
SubLineIndex = subLineIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// True if this line is code or data.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsCodeOrData {
|
|
|
|
|
get {
|
|
|
|
|
return LineType == Type.Code || LineType == Type.Data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the specified offset is represented by this line. There
|
|
|
|
|
/// will be only one code/data line for a given offset, but there may be
|
|
|
|
|
/// multiple others (comments, notes, etc.) associated with it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public bool Contains(int offset) {
|
|
|
|
|
// Note OffsetSpan can be zero.
|
|
|
|
|
return (offset == FileOffset ||
|
|
|
|
|
(offset >= FileOffset && offset < FileOffset + OffsetSpan));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString() {
|
|
|
|
|
return "Line type=" + LineType + " off=+" + FileOffset.ToString("x6") +
|
|
|
|
|
" span=" + OffsetSpan;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Captures the set of selected lines. Lines are identified by offset and type.
|
|
|
|
|
///
|
|
|
|
|
/// The idea is to save the selection, rebuild the list -- potentially moving
|
|
|
|
|
/// stuff around -- and then rebuild the selection bitmap by finding matching
|
|
|
|
|
/// items.
|
|
|
|
|
///
|
|
|
|
|
/// We don't try to identify parts of multi-line things. If you've selected
|
|
|
|
|
/// part of a multi-line string, then when we restore the selection you'll have
|
|
|
|
|
/// the entire string selected. For the operations that are possible across
|
|
|
|
|
/// multiple offsets, this seems like reasonable behavior.
|
|
|
|
|
///
|
|
|
|
|
/// We can't precisely restore the selection in terms of which file offsets
|
2020-10-15 21:03:32 +00:00
|
|
|
|
/// are selected. If you select one byte and apply an analyzer tag, we'll restore
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// the selection to a line with 1-4 bytes. This gets weird if you hit "undo",
|
|
|
|
|
/// as you will then have 1-4 bytes selected rather than the original one. It
|
|
|
|
|
/// might be better to just clear the selection on "undo".
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SavedSelection {
|
|
|
|
|
private class Tag {
|
|
|
|
|
public int mOffset;
|
|
|
|
|
public int mSpan;
|
|
|
|
|
public Line.Type mTypes;
|
|
|
|
|
|
|
|
|
|
public Tag(int offset, int span, Line.Type lineType) {
|
|
|
|
|
//Debug.Assert(offset >= 0);
|
|
|
|
|
Debug.Assert(span >= 0);
|
|
|
|
|
mOffset = offset;
|
|
|
|
|
mSpan = (span == 0) ? 1 : span;
|
|
|
|
|
mTypes = lineType;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Tag> mSelectionTags = new List<Tag>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This is a place to save the file offset associated with the ListView's
|
|
|
|
|
/// TopItem, so we can position the list appropriately.
|
|
|
|
|
/// </summary>
|
2019-10-10 18:57:36 +00:00
|
|
|
|
private NavStack.Location mTopPosition;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
// Use Generate().
|
|
|
|
|
private SavedSelection() { }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new SavedSelection object, generating a list of tags from the
|
|
|
|
|
/// lines that are currently selected.
|
|
|
|
|
///
|
|
|
|
|
/// If nothing is selected, SavedSelection will have no members.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="dl">Display list, with list of Lines.</param>
|
|
|
|
|
/// <param name="sel">Bit vector specifying which lines are selected.</param>
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
/// <param name="topIndex">Index of line that appears at the top of the list
|
|
|
|
|
/// control.</param>
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// <returns>New SavedSelection object.</returns>
|
2019-06-08 00:25:04 +00:00
|
|
|
|
public static SavedSelection Generate(LineListGen dl, DisplayListSelection sel,
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
int topIndex) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
SavedSelection savedSel = new SavedSelection();
|
|
|
|
|
//Debug.Assert(topOffset >= 0);
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
|
|
|
|
|
int topOffset = dl[topIndex].FileOffset;
|
|
|
|
|
int firstIndex = dl.FindLineIndexByOffset(topOffset);
|
|
|
|
|
Debug.Assert(topIndex >= firstIndex);
|
2019-10-10 18:57:36 +00:00
|
|
|
|
savedSel.mTopPosition =
|
|
|
|
|
new NavStack.Location(topOffset, topIndex - firstIndex, false);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
List<Line> lineList = dl.mLineList;
|
|
|
|
|
Debug.Assert(lineList.Count == sel.Length);
|
|
|
|
|
|
|
|
|
|
// Generate tags, which are a combination of the offset, span, and a merge
|
|
|
|
|
// of types of all the lines associated with that offset.
|
|
|
|
|
//
|
|
|
|
|
// We may want to consider some sort of optimization for a "select all"
|
|
|
|
|
// operation, although there aren't many changes you can make after selecting
|
|
|
|
|
// all lines in a very large file.
|
|
|
|
|
Tag tag = null;
|
|
|
|
|
int curOffset = -1;
|
|
|
|
|
for (int i = 0; i < lineList.Count; i++) {
|
|
|
|
|
if (!sel[i]) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Line line = lineList[i];
|
2020-10-15 21:03:32 +00:00
|
|
|
|
// Code start/stop tags can transform code to data and vice-versa, so we
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// want the tag to reflect the fact that both could exist.
|
|
|
|
|
Line.Type lineType = line.LineType;
|
|
|
|
|
if (lineType == Line.Type.Code || lineType == Line.Type.Data) {
|
|
|
|
|
lineType = Line.Type.CodeOrData;
|
|
|
|
|
}
|
|
|
|
|
if (line.FileOffset != curOffset) {
|
|
|
|
|
// advanced to new offset, flush previous
|
|
|
|
|
if (tag != null) {
|
|
|
|
|
savedSel.mSelectionTags.Add(tag);
|
|
|
|
|
}
|
|
|
|
|
curOffset = line.FileOffset;
|
|
|
|
|
|
|
|
|
|
tag = new Tag(line.FileOffset, line.OffsetSpan, lineType);
|
|
|
|
|
} else {
|
|
|
|
|
// another item at same offset
|
|
|
|
|
tag.mSpan = Math.Max(tag.mSpan, line.OffsetSpan);
|
|
|
|
|
tag.mTypes |= lineType;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (curOffset == -1) {
|
|
|
|
|
// It's hard to cause an action that requires save/restore when you don't
|
|
|
|
|
// have anything selected in the ListView. However, this can happen if
|
|
|
|
|
// you do a sequence like:
|
|
|
|
|
// - Open a file that starts with a JMP followed by data.
|
|
|
|
|
// - Click on the blank line below the code, which has the code's offset,
|
2020-10-15 21:03:32 +00:00
|
|
|
|
// and select "remove atags". This causes the blank line to vanish,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// so the Restore() won't select anything.
|
|
|
|
|
// - Click "undo".
|
|
|
|
|
Debug.WriteLine("NOTE: no selection found");
|
|
|
|
|
} else {
|
|
|
|
|
// Add the in-progress tag to the list.
|
|
|
|
|
savedSel.mSelectionTags.Add(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return savedSel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a selection set by identifying the set of lines in the display list
|
|
|
|
|
/// that correspond to items in the SavedSelection tag list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="dl">Display list, with list of Lines.</param>
|
|
|
|
|
/// <returns>Set of selected lines.</returns>
|
2019-06-08 00:25:04 +00:00
|
|
|
|
public DisplayListSelection Restore(LineListGen dl, out int topIndex) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
List<Line> lineList = dl.mLineList;
|
2019-06-08 00:25:04 +00:00
|
|
|
|
DisplayListSelection sel = new DisplayListSelection(lineList.Count);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
topIndex = -1;
|
|
|
|
|
|
|
|
|
|
// Walk through the tag list, which is ordered by ascending offset, and
|
|
|
|
|
// through the display list, which is similarly ordered.
|
|
|
|
|
int tagIndex = 0;
|
|
|
|
|
int lineIndex = 0;
|
|
|
|
|
while (tagIndex < mSelectionTags.Count && lineIndex < lineList.Count) {
|
|
|
|
|
Tag tag = mSelectionTags[tagIndex];
|
|
|
|
|
int lineOffset = lineList[lineIndex].FileOffset;
|
|
|
|
|
|
|
|
|
|
// If a line encompassing this offset was at the top of the ListView
|
|
|
|
|
// control before, use this line's index as the top.
|
2019-10-10 18:57:36 +00:00
|
|
|
|
if (topIndex < 0 && lineList[lineIndex].Contains(mTopPosition.Offset)) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
topIndex = lineIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lineOffset >= tag.mOffset && lineOffset < tag.mOffset + tag.mSpan) {
|
|
|
|
|
// Intersection. If the line type matches, add it to the set.
|
2019-10-10 18:57:36 +00:00
|
|
|
|
// TODO(someday): this is doing the wrong thing when we have more
|
|
|
|
|
// than one blank line at an offset.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if ((tag.mTypes & lineList[lineIndex].LineType) != 0) {
|
|
|
|
|
sel[lineIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Advance to the next line entry.
|
|
|
|
|
lineIndex++;
|
|
|
|
|
} else if (tag.mOffset < lineOffset) {
|
|
|
|
|
// advance tag
|
|
|
|
|
tagIndex++;
|
|
|
|
|
} else {
|
|
|
|
|
Debug.Assert(tag.mOffset > lineOffset);
|
|
|
|
|
lineIndex++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Continue search for topIndex, if necessary.
|
|
|
|
|
while (topIndex < 0 && lineIndex < lineList.Count) {
|
2019-10-10 18:57:36 +00:00
|
|
|
|
if (lineList[lineIndex].Contains(mTopPosition.Offset)) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
topIndex = lineIndex;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
lineIndex++;
|
|
|
|
|
}
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
Debug.WriteLine("TopOffset " + mTopPosition + " --> index " + topIndex);
|
|
|
|
|
|
|
|
|
|
// Adjust position within an element. This is necessary so we don't jump to
|
|
|
|
|
// the top of multi-line long comments or notes whenever any part of that
|
|
|
|
|
// comment or note is at the top of the list.
|
|
|
|
|
if (topIndex >= 0 && mTopPosition.LineDelta > 0) {
|
|
|
|
|
int adjIndex = topIndex + mTopPosition.LineDelta;
|
|
|
|
|
if (adjIndex >= lineList.Count ||
|
2019-10-10 18:57:36 +00:00
|
|
|
|
lineList[adjIndex].FileOffset != mTopPosition.Offset) {
|
Improve save & restore of top line
Whenever the display list gets regenerated, we need to restore the
code list view scroll position to the previous location in the file.
This gets tricky when multiple lines are appearing or disappearing.
We were saving the file offset of the line, but that works poorly
when there's a multi-line comment associated with that offset,
because we end up scrolling to the top of the comment whenever any
part of the comment is at the top of the screen.
We now track the file offset and the number of lines we were from
the top of that offset's content. This works well unless we remove
a lot of lines. If the adjusted line index would put us into a
different file offset, we punt and just scroll to the top of the item.
Also, fix a crasher in Edit Note.
Also, fix behavior when the list shrinks while a line near the end
of the file is selected.
Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a
common constant.
2019-07-17 20:47:43 +00:00
|
|
|
|
Debug.WriteLine("Can't adjust top position");
|
|
|
|
|
// can't adjust; maybe they deleted several lines from comment
|
|
|
|
|
} else {
|
|
|
|
|
topIndex = adjIndex;
|
|
|
|
|
Debug.WriteLine("Top index adjusted to " + adjIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (topIndex < 0) {
|
|
|
|
|
// This can happen if you delete the header comment while scrolled
|
|
|
|
|
// to the top of the list.
|
|
|
|
|
topIndex = 0;
|
|
|
|
|
}
|
|
|
|
|
return sel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DebugDump() {
|
|
|
|
|
Debug.WriteLine("Selection (" + mSelectionTags.Count + " offsets):");
|
|
|
|
|
foreach (Tag tag in mSelectionTags) {
|
|
|
|
|
Debug.WriteLine(" +" + tag.mOffset.ToString("x6") + "/" +
|
|
|
|
|
tag.mSpan + ": " + tag.mTypes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="proj">Project object.</param>
|
|
|
|
|
/// <param name="formatter">Formatter object.</param>
|
2019-05-28 01:46:09 +00:00
|
|
|
|
public LineListGen(DisasmProject proj, DisplayList displayList, Formatter formatter,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
PseudoOp.PseudoOpNames opNames) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
Debug.Assert(proj != null);
|
|
|
|
|
Debug.Assert(displayList != null);
|
|
|
|
|
Debug.Assert(formatter != null);
|
|
|
|
|
Debug.Assert(opNames != null);
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
mProject = proj;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
mDisplayList = displayList;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
mFormatter = formatter;
|
|
|
|
|
mPseudoOpNames = opNames;
|
|
|
|
|
|
|
|
|
|
mLineList = new List<Line>();
|
2019-08-17 23:59:08 +00:00
|
|
|
|
mFormattedLineCache = new FormattedOperandCache();
|
2020-08-27 20:13:58 +00:00
|
|
|
|
mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.FMT_SHOW_CYCLE_COUNTS,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
false);
|
Label rework, part 6
Correct handling of local variables. We now correctly uniquify them
with regard to non-unique labels. Because local vars can effectively
have global scope we mostly want to treat them as global, but they're
uniquified relative to other globals very late in the process, so we
can't just throw them in the symbol table and be done. Fortunately
local variables exist in a separate namespace, so we just need to
uniquify the variables relative to the post-localization symbol table.
In other words, we take the symbol table, apply the label map, and
rename any variable that clashes.
This also fixes an older problem where we weren't masking the
leading '_' on variable labels when generating 64tass output.
The code list now makes non-unique labels obvious, but you can't tell
the difference between unique global and unique local. What's more,
the default type value in Edit Label is now adjusted to Global for
unique locals that were auto-generated. To make it a bit easier to
figure out what's what, the Info panel now has a "label type" line
that reports the type.
The 2023-non-unique-labels test had some additional tests added to
exercise conflicts with local variables. The 2019-local-variables
test output changed slightly because the de-duplicated variable
naming convention was simplified.
2019-11-18 21:26:03 +00:00
|
|
|
|
mLvLookup = new LocalVariableLookup(mProject.LvTables, mProject, null, false, false);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
mDisplayList.ListGen = this;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-06-09 00:13:11 +00:00
|
|
|
|
/// Changes the Formatter object. Clears the line list, instigating a full re-render.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="formatter">Formatter object.</param>
|
|
|
|
|
public void SetFormatter(Formatter formatter) {
|
|
|
|
|
mFormatter = formatter;
|
|
|
|
|
mLineList.Clear();
|
2019-06-09 00:13:11 +00:00
|
|
|
|
mDisplayList.Clear();
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
// We probably just changed settings, so update this as well.
|
2020-08-27 20:13:58 +00:00
|
|
|
|
mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.FMT_SHOW_CYCLE_COUNTS,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-06-09 00:13:11 +00:00
|
|
|
|
/// Changes the pseudo-op name object. Clears the line list, instigating a
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
/// full re-render. If the new set is unchanged from the old set, nothing is done.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="opNames">Pseudo-op names.</param>
|
|
|
|
|
public void SetPseudoOpNames(PseudoOp.PseudoOpNames opNames) {
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
if (mPseudoOpNames == opNames) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
mPseudoOpNames = opNames;
|
Various improvements
The PseudoOpNames class is increasingly being used in situations
where mutability is undesirable. This change makes instances
immutable, eliminating the Copy() method and adding a constructor
that takes a Dictionary. The serialization code now operates on a
Dictionary instead of the class properties, but the JSON encoding is
identical, so this doesn't invalidate app settings file data.
Added an equality test to PseudoOpNames. In LineListGen, don't
reset the line list if the names haven't actually changed.
Use a table lookup for C64 character conversions. I figure that
should be faster than multiple conditionals on a modern x64 system.
Fixed a 64tass generator issue where we tried to query project
properties in a call that might not have a project available
(specifically, getting FormatConfig values out of the generator for
use in the "quick set" buttons for Display Format).
Fixed a regression test harness issue where, if the assembler reported
success but didn't actually generate output, an exception would be
thrown that halted the tests.
Increased the width of text entry fields on the Pseudo-Op tab of app
settings. The previous 8-character limit wasn't wide enough to hold
ACME's "!pseudopc". Also, use TrimEnd() to remove trailing spaces
(leading spaces are still allowed).
In the last couple of months, Win10 started stalling for a fraction
of a second when executing assemblers. It doesn't do this every
time; mostly it happens if it has been a while since the assembler
was run. My guess is this has to do with changes to the built-in
malware scanner. Whatever the case, we now change the mouse pointer
to a wait cursor while updating the assembler version cache.
2019-08-17 18:14:05 +00:00
|
|
|
|
Debug.Assert(mPseudoOpNames == opNames);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
mLineList.Clear();
|
2019-06-09 00:13:11 +00:00
|
|
|
|
mDisplayList.Clear();
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Number of lines in the list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int Count { get { return mLineList.Count; } }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the Nth element.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Line this[int key] {
|
|
|
|
|
get {
|
|
|
|
|
return mLineList[key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the Line's FormattedParts object, generating it first if necessary.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>Object with formatted strings.</returns>
|
|
|
|
|
public FormattedParts GetFormattedParts(int index) {
|
|
|
|
|
Line line = mLineList[index];
|
|
|
|
|
if (line.Parts == null) {
|
|
|
|
|
FormattedParts parts;
|
|
|
|
|
switch (line.LineType) {
|
|
|
|
|
case Line.Type.Code:
|
2019-08-17 23:59:08 +00:00
|
|
|
|
parts = GenerateInstructionLine(line.FileOffset, line.OffsetSpan);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case Line.Type.Data:
|
2019-08-17 23:59:08 +00:00
|
|
|
|
parts = GenerateDataLine(line.FileOffset, line.SubLineIndex);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
break;
|
2019-08-27 23:45:37 +00:00
|
|
|
|
case Line.Type.LocalVariableTable:
|
|
|
|
|
parts = GenerateLvTableLine(line.FileOffset, line.SubLineIndex);
|
|
|
|
|
break;
|
2019-11-23 04:45:57 +00:00
|
|
|
|
case Line.Type.VisualizationSet:
|
|
|
|
|
mProject.VisualizationSets.TryGetValue(line.FileOffset,
|
|
|
|
|
out VisualizationSet visSet);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
parts = FormattedParts.CreateVisualizationSet(visSet);
|
2019-11-23 04:45:57 +00:00
|
|
|
|
break;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
case Line.Type.Blank:
|
|
|
|
|
// Nothing to do.
|
|
|
|
|
parts = FormattedParts.CreateBlankLine();
|
|
|
|
|
break;
|
|
|
|
|
case Line.Type.OrgDirective:
|
|
|
|
|
case Line.Type.RegWidthDirective:
|
2020-07-09 22:17:47 +00:00
|
|
|
|
case Line.Type.DataBankDirective:
|
2019-05-11 17:16:54 +00:00
|
|
|
|
case Line.Type.LongComment:
|
|
|
|
|
case Line.Type.Note:
|
|
|
|
|
// should have been done already
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
parts = FormattedParts.Create("x", "x", "x", "x", "x", "x", "x", "x", "x");
|
2019-05-11 17:16:54 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
line.Parts = parts;
|
|
|
|
|
}
|
|
|
|
|
return line.Parts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a string with the concatenation of the searchable portions of the line.
|
|
|
|
|
/// Different sections are separated with an unlikely unicode character. The goal
|
|
|
|
|
/// is to have a single string per line that can be searched quickly, without having
|
|
|
|
|
/// adjacent fields spill into each other.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="index">Line index.</param>
|
|
|
|
|
/// <returns>Formatted line contents.</returns>
|
|
|
|
|
public string GetSearchString(int index) {
|
|
|
|
|
Line line = mLineList[index];
|
|
|
|
|
if (line.SearchString == null) {
|
|
|
|
|
const char sep = '\u203b'; // REFERENCE MARK
|
|
|
|
|
|
|
|
|
|
FormattedParts parts = GetFormattedParts(index);
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
// Some parts may be null, e.g. for long comments. Append() can deal.
|
|
|
|
|
sb.Append(parts.Label);
|
|
|
|
|
sb.Append(sep);
|
|
|
|
|
sb.Append(parts.Opcode);
|
|
|
|
|
sb.Append(sep);
|
|
|
|
|
sb.Append(parts.Operand);
|
|
|
|
|
sb.Append(sep);
|
|
|
|
|
sb.Append(parts.Comment);
|
|
|
|
|
line.SearchString = sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
return line.SearchString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the first line entry that encompasses the specified offset.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset">Offset to search for. Negative values are allowed.</param>
|
|
|
|
|
/// <returns>Line list index, or -1 if not found.</returns>
|
|
|
|
|
private static int FindLineByOffset(List<Line> lineList, int offset) {
|
|
|
|
|
if (lineList.Count == 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int low = 0;
|
|
|
|
|
int high = lineList.Count - 1;
|
|
|
|
|
int mid = -1;
|
|
|
|
|
bool found = false;
|
|
|
|
|
while (low <= high) {
|
|
|
|
|
mid = (low + high) / 2;
|
|
|
|
|
Line line = lineList[mid];
|
|
|
|
|
|
|
|
|
|
if (line.Contains(offset)) {
|
|
|
|
|
// found a match
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
} else if (line.FileOffset > offset) {
|
|
|
|
|
// too big, move the high end in
|
|
|
|
|
high = mid - 1;
|
|
|
|
|
} else if (line.FileOffset < offset) {
|
|
|
|
|
// too small, move the low end in
|
|
|
|
|
low = mid + 1;
|
|
|
|
|
} else {
|
|
|
|
|
// WTF
|
|
|
|
|
throw new Exception("Bad binary search");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We found *a* matching line. Seek backward to find the *first* matching line.
|
|
|
|
|
while (mid > 0) {
|
|
|
|
|
Line upLine = lineList[mid - 1];
|
|
|
|
|
if (upLine.Contains(offset)) {
|
|
|
|
|
mid--;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the first line entry that encompasses the specified offset.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset">Offset to search for.</param>
|
|
|
|
|
/// <returns>Line list index, or -1 if not found.</returns>
|
|
|
|
|
public int FindLineIndexByOffset(int offset) {
|
|
|
|
|
return FindLineByOffset(mLineList, offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the code or data line entry that encompasses the specified offset.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset">Offset to search for.</param>
|
|
|
|
|
/// <returns>Line list index, or -1 if not found.</returns>
|
|
|
|
|
public int FindCodeDataIndexByOffset(int offset) {
|
|
|
|
|
if (offset < 0) {
|
|
|
|
|
// Header offset. No code or data here.
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
int index = FindLineByOffset(mLineList, offset);
|
|
|
|
|
if (index < 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
while (mLineList[index].LineType != Line.Type.Code &&
|
|
|
|
|
mLineList[index].LineType != Line.Type.Data) {
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
return index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generates Lines for the entire project.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void GenerateAll() {
|
2019-10-23 20:00:46 +00:00
|
|
|
|
// Do this now in case the project properties have changed.
|
|
|
|
|
ConfigureCharacterEncoding();
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
mLineList.Clear();
|
2019-10-23 20:00:46 +00:00
|
|
|
|
|
2019-07-07 23:18:46 +00:00
|
|
|
|
List<Line> headerLines = GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames);
|
|
|
|
|
mLineList.InsertRange(0, headerLines);
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
GenerateLineList(0, mProject.FileData.Length - 1, mLineList);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
mDisplayList.ResetList(mLineList.Count);
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
Debug.Assert(ValidateLineList(), "Display list failed validation");
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-23 20:00:46 +00:00
|
|
|
|
private void ConfigureCharacterEncoding() {
|
|
|
|
|
TextScanMode textMode = mProject.ProjectProps.AnalysisParams.DefaultTextScanMode;
|
|
|
|
|
switch (textMode) {
|
|
|
|
|
case TextScanMode.LowAscii:
|
|
|
|
|
mCharTest = CharEncoding.IsPrintableAscii;
|
|
|
|
|
mCharConv = CharEncoding.ConvertAscii;
|
|
|
|
|
break;
|
|
|
|
|
case TextScanMode.LowHighAscii:
|
|
|
|
|
mCharTest = CharEncoding.IsPrintableLowOrHighAscii;
|
|
|
|
|
mCharConv = CharEncoding.ConvertLowAndHighAscii;
|
|
|
|
|
break;
|
|
|
|
|
case TextScanMode.C64Petscii:
|
|
|
|
|
mCharTest = CharEncoding.IsPrintableC64Petscii;
|
|
|
|
|
mCharConv = CharEncoding.ConvertC64Petscii;
|
|
|
|
|
break;
|
|
|
|
|
case TextScanMode.C64ScreenCode:
|
|
|
|
|
mCharTest = CharEncoding.IsPrintableC64ScreenCode;
|
|
|
|
|
mCharConv = CharEncoding.ConvertC64ScreenCode;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
mCharTest = CharEncoding.IsPrintableAscii;
|
|
|
|
|
mCharConv = CharEncoding.ConvertAscii;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generates a list of Lines for the specified range of offsets, replacing
|
|
|
|
|
/// existing values.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="startOffset">First offset. Must be the start of an instruction
|
|
|
|
|
/// or data area.</param>
|
|
|
|
|
/// <param name="endOffset">End offset (inclusive).</param>
|
|
|
|
|
public void GenerateRange(int startOffset, int endOffset) {
|
|
|
|
|
if (startOffset < 0) {
|
2019-07-07 23:18:46 +00:00
|
|
|
|
// Clear existing header lines. Find the first non-header item.
|
|
|
|
|
int headerEndIndex = FindLineByOffset(mLineList, 0);
|
|
|
|
|
if (headerEndIndex == 0) {
|
|
|
|
|
// no header lines present
|
|
|
|
|
Debug.WriteLine("No header lines found");
|
|
|
|
|
} else {
|
|
|
|
|
Debug.WriteLine("Removing " + headerEndIndex + " header lines");
|
|
|
|
|
mLineList.RemoveRange(0, headerEndIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Line> headerLines = GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames);
|
|
|
|
|
mLineList.InsertRange(0, headerLines);
|
|
|
|
|
mDisplayList.ClearListSegment(0, headerEndIndex, headerLines.Count);
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (endOffset < 0) {
|
2019-07-07 23:18:46 +00:00
|
|
|
|
// nothing else to do -- header-only change
|
2019-05-11 17:16:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// do the rest
|
|
|
|
|
startOffset = 0;
|
|
|
|
|
}
|
|
|
|
|
Debug.Assert(startOffset >= 0);
|
|
|
|
|
Debug.Assert(endOffset < mProject.FileData.Length);
|
|
|
|
|
Debug.Assert(endOffset >= startOffset);
|
|
|
|
|
//Debug.WriteLine("DL gen range [" + startOffset + "," + endOffset + "]");
|
|
|
|
|
|
|
|
|
|
// Find the start index. The start offset should always appear at the
|
2019-07-07 00:24:42 +00:00
|
|
|
|
// start of a Line (as opposed to being in the middle of a multi-byte instruction)
|
|
|
|
|
// because it comes from item selection.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
int startIndex = FindLineByOffset(mLineList, startOffset);
|
|
|
|
|
if (startIndex < 0) {
|
|
|
|
|
Debug.Assert(false, "Unable to find startOffset " + startOffset);
|
|
|
|
|
GenerateAll();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Find the end index. The end offset can be part of a multi-line data item, like
|
|
|
|
|
// a long string. Find the first Line that starts at an offset larger than endOffset.
|
|
|
|
|
int endIndex;
|
|
|
|
|
if (startOffset == endOffset) {
|
|
|
|
|
// Simple optimization for single-offset groups.
|
|
|
|
|
endIndex = startIndex;
|
|
|
|
|
} else {
|
|
|
|
|
endIndex = FindLineByOffset(mLineList, endOffset);
|
|
|
|
|
}
|
|
|
|
|
if (endIndex < 0) {
|
|
|
|
|
Debug.Assert(false, "Unable to find endOffset " + endOffset);
|
|
|
|
|
GenerateAll();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// There may be more than one line involved, so we need to scan forward.
|
|
|
|
|
for (endIndex++; endIndex < mLineList.Count; endIndex++) {
|
|
|
|
|
if (mLineList[endIndex].FileOffset > endOffset) {
|
|
|
|
|
endIndex--;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (endIndex == mLineList.Count) {
|
|
|
|
|
// whoops, loop ended before we had a chance to decrement
|
|
|
|
|
endIndex = mLineList.Count - 1;
|
|
|
|
|
}
|
|
|
|
|
Debug.WriteLine("GenerateRange: offset [+" + startOffset.ToString("x6") + ",+" +
|
|
|
|
|
endOffset.ToString("x6") +
|
|
|
|
|
"] maps to index [" + startIndex + "," + endIndex + "]");
|
|
|
|
|
Debug.Assert(endIndex >= startIndex);
|
|
|
|
|
|
|
|
|
|
// Create temporary list to hold new lines. Set the initial capacity to
|
|
|
|
|
// the previous size, on the assumption that it won't change much.
|
|
|
|
|
List<Line> newLines = new List<Line>(endIndex - startIndex + 1);
|
2019-08-25 00:35:26 +00:00
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
GenerateLineList(startOffset, endOffset, newLines);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
// Out with the old, in with the new.
|
|
|
|
|
mLineList.RemoveRange(startIndex, endIndex - startIndex + 1);
|
|
|
|
|
mLineList.InsertRange(startIndex, newLines);
|
2019-07-07 00:24:42 +00:00
|
|
|
|
mDisplayList.ClearListSegment(startIndex, endIndex - startIndex + 1, newLines.Count);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
Debug.Assert(ValidateLineList(), "Display list failed validation");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Validates the line list, confirming that every offset is represented exactly once.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>True if all is well.</returns>
|
|
|
|
|
private bool ValidateLineList() {
|
|
|
|
|
int expectedOffset = 0;
|
|
|
|
|
int lastOffset = Int32.MinValue;
|
|
|
|
|
foreach (Line line in mLineList) {
|
|
|
|
|
// Header lines aren't guaranteed to be sequential and don't have a span.
|
|
|
|
|
// They are expected to be in sorted order, and to be unique (with the
|
|
|
|
|
// notable exception of the header comment, which is multi-line).
|
|
|
|
|
if (line.FileOffset < 0) {
|
|
|
|
|
if (line.FileOffset < lastOffset || (line.LineType != Line.Type.LongComment &&
|
|
|
|
|
line.FileOffset == lastOffset)) {
|
|
|
|
|
Debug.WriteLine("Header offsets went backward: cur=" +
|
|
|
|
|
line.FileOffset + " last=" + lastOffset);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
lastOffset = line.FileOffset;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Blank lines and comments can appear before or after code/data. They
|
|
|
|
|
// must have the offset of the associated line, and a span of zero.
|
|
|
|
|
if (line.FileOffset != expectedOffset && line.FileOffset != lastOffset) {
|
|
|
|
|
Debug.WriteLine("ValidateLineList: bad offset " + line.FileOffset +
|
|
|
|
|
" (last=" + lastOffset + ", expected next=" + expectedOffset + ")");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line.SubLineIndex != 0) {
|
|
|
|
|
// In the middle of a multi-line thing, don't advance last/expected.
|
|
|
|
|
Debug.Assert(line.FileOffset == lastOffset);
|
|
|
|
|
} else {
|
|
|
|
|
lastOffset = expectedOffset;
|
|
|
|
|
expectedOffset += line.OffsetSpan;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (expectedOffset != mProject.FileData.Length) {
|
|
|
|
|
Debug.WriteLine("ValidateLineList: did not cover entire file: last offset " +
|
|
|
|
|
expectedOffset + ", file has " + mProject.FileData.Length);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-10-10 20:49:21 +00:00
|
|
|
|
/// Generates a synthetic offset for the FileOffset field from an index value.
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// (The exact algorithm isn't too important, as these offsets are not stored in the
|
|
|
|
|
/// project file.)
|
|
|
|
|
/// </summary>
|
2019-10-10 20:49:21 +00:00
|
|
|
|
/// <param name="index">Index into DisasmProject.ActiveDefSymbolListlist.</param>
|
|
|
|
|
/// <returns>Synthetic file offset. Value will be < 0.</returns>
|
|
|
|
|
public static int DefSymOffsetFromIndex(int index) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
Debug.Assert(index >= 0 && index < (1 << 24));
|
|
|
|
|
return index - (1 << 24);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the DisasmProject.ActiveDefSymbolList index for an EQU line with
|
|
|
|
|
/// the specified file offset.
|
|
|
|
|
/// </summary>
|
2019-10-10 20:49:21 +00:00
|
|
|
|
/// <param name="offset">Synthetic file offset, from DefSymOffsetFromIndex().</param>
|
|
|
|
|
/// <returns>Index into DisasmProject.ActiveDefSymbolListlist.</returns>
|
2019-05-11 17:16:54 +00:00
|
|
|
|
public static int DefSymIndexFromOffset(int offset) {
|
|
|
|
|
Debug.Assert(offset < 0);
|
|
|
|
|
return offset + (1 << 24);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 01:11:48 +00:00
|
|
|
|
// NOTE: the two functions above are tied to the implementation of the function
|
|
|
|
|
// below: we output the lines in the order in which they appear in ActiveDefSymbolList.
|
|
|
|
|
// If we want to get fancy and sort them, we'll need to do some additional work.
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// <summary>
|
2019-07-07 23:18:46 +00:00
|
|
|
|
/// Generates the header lines (header comment, EQU directives).
|
2019-05-11 17:16:54 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="proj">Project reference.</param>
|
|
|
|
|
/// <param name="formatter">Output formatter.</param>
|
|
|
|
|
/// <param name="opNames">Pseudo-op names.</param>
|
2019-07-07 23:18:46 +00:00
|
|
|
|
/// <returns>List with header lines.</returns>
|
|
|
|
|
private static List<Line> GenerateHeaderLines(DisasmProject proj, Formatter formatter,
|
|
|
|
|
PseudoOp.PseudoOpNames opNames) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
List<Line> tmpLines = new List<Line>();
|
|
|
|
|
Line line;
|
|
|
|
|
|
|
|
|
|
// Check for header comment.
|
|
|
|
|
if (proj.LongComments.TryGetValue(Line.HEADER_COMMENT_OFFSET,
|
|
|
|
|
out MultiLineComment headerComment)) {
|
|
|
|
|
List<string> formatted = headerComment.FormatText(formatter, string.Empty);
|
|
|
|
|
StringListToLines(formatted, Line.HEADER_COMMENT_OFFSET, Line.Type.LongComment,
|
2019-10-13 00:23:32 +00:00
|
|
|
|
CommonWPF.Helper.ZeroColor, 1.0f, tmpLines);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Format symbols.
|
|
|
|
|
int index = 0;
|
|
|
|
|
foreach (DefSymbol defSym in proj.ActiveDefSymbolList) {
|
|
|
|
|
line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.EquDirective);
|
|
|
|
|
// Use an operand length of 1 so things are shown as concisely as possible.
|
|
|
|
|
string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
|
|
|
|
|
null, defSym.DataDescriptor, defSym.Value, 1,
|
|
|
|
|
PseudoOp.FormatNumericOpFlags.None);
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
valueStr = PseudoOp.AnnotateEquDirective(formatter, valueStr, defSym);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
string comment = formatter.FormatEolComment(defSym.Comment);
|
2019-11-13 01:24:41 +00:00
|
|
|
|
FormattedParts parts = FormattedParts.CreateEquDirective(
|
|
|
|
|
defSym.GenerateDisplayLabel(formatter),
|
2019-05-11 17:16:54 +00:00
|
|
|
|
formatter.FormatPseudoOp(opNames.EquDirective),
|
|
|
|
|
valueStr, comment);
|
|
|
|
|
line.Parts = parts;
|
|
|
|
|
tmpLines.Add(line);
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (proj.ActiveDefSymbolList.Count != 0) {
|
|
|
|
|
// We had some EQUs, throw a blank line at the end.
|
|
|
|
|
index++;
|
|
|
|
|
line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.Blank);
|
|
|
|
|
tmpLines.Add(line);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 23:18:46 +00:00
|
|
|
|
return tmpLines;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generates lines for the specified range of file offsets.
|
|
|
|
|
///
|
|
|
|
|
/// Does not generate formatted parts in most cases; that usually happens on demand.
|
|
|
|
|
/// Complicated items, such as word-wrapped long comments, may be generated now
|
|
|
|
|
/// and saved off.
|
|
|
|
|
///
|
|
|
|
|
/// This still needs a formatter arg even when no text is rendered because some
|
|
|
|
|
/// options, like maximum per-line operand length, might affect how many lines
|
|
|
|
|
/// are generated.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="startOffset">Offset of first byte.</param>
|
|
|
|
|
/// <param name="endOffset">Offset of last byte.</param>
|
|
|
|
|
/// <param name="lines">List to add output lines to.</param>
|
2019-08-17 23:59:08 +00:00
|
|
|
|
private void GenerateLineList(int startOffset, int endOffset, List<Line> lines) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
//Debug.WriteLine("GenerateRange [+" + startOffset.ToString("x6") + ",+" +
|
|
|
|
|
// endOffset.ToString("x6") + "]");
|
|
|
|
|
|
2020-08-22 20:47:52 +00:00
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
Debug.Assert(startOffset >= 0);
|
|
|
|
|
Debug.Assert(endOffset >= startOffset);
|
|
|
|
|
|
2019-08-31 01:33:05 +00:00
|
|
|
|
// Assume variables may have changed.
|
|
|
|
|
mLvLookup.Reset();
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// Find the previous status flags for M/X tracking.
|
|
|
|
|
StatusFlags prevFlags = StatusFlags.AllIndeterminate;
|
2019-08-17 23:59:08 +00:00
|
|
|
|
if (mProject.CpuDef.HasEmuFlag) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
for (int scanoff = startOffset - 1; scanoff >= 0; scanoff--) {
|
2019-08-17 23:59:08 +00:00
|
|
|
|
Anattrib attr = mProject.GetAnattrib(scanoff);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (attr.IsInstructionStart) {
|
|
|
|
|
prevFlags = attr.StatusFlags;
|
|
|
|
|
// Apply the same tweak here that we do to curFlags below.
|
2020-07-06 15:27:42 +00:00
|
|
|
|
prevFlags.M = attr.StatusFlags.IsShortM ? 1 : 0;
|
|
|
|
|
prevFlags.X = attr.StatusFlags.IsShortX ? 1 : 0;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
Debug.WriteLine("GenerateLineList startOff=+" +
|
|
|
|
|
startOffset.ToString("x6") + " using initial flags from +" +
|
|
|
|
|
scanoff.ToString("x6") + ": " + prevFlags);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configure the initial value of addBlank. The specific case we're handling is
|
|
|
|
|
// a no-continue instruction (e.g. JMP) followed by an instruction with a label.
|
|
|
|
|
// When we rename the label, we don't want the blank to disappear during the
|
|
|
|
|
// partial-list generation.
|
|
|
|
|
bool addBlank = false;
|
|
|
|
|
if (startOffset > 0) {
|
2019-08-17 23:59:08 +00:00
|
|
|
|
int baseOff = DataAnalysis.GetBaseOperandOffset(mProject, startOffset - 1);
|
|
|
|
|
if (mProject.GetAnattrib(baseOff).DoesNotContinue) {
|
2020-08-22 20:47:52 +00:00
|
|
|
|
// TODO(someday): ideally the blank line would come after inline data
|
|
|
|
|
// that follows a no-continue JSR/JSL/BRK
|
2019-05-11 17:16:54 +00:00
|
|
|
|
addBlank = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-25 00:35:26 +00:00
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
int offset = startOffset;
|
|
|
|
|
while (offset <= endOffset) {
|
2019-08-17 23:59:08 +00:00
|
|
|
|
Anattrib attr = mProject.GetAnattrib(offset);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (attr.IsInstructionStart && offset > 0 &&
|
2019-08-17 23:59:08 +00:00
|
|
|
|
mProject.GetAnattrib(offset - 1).IsData) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// Transition from data to code. (Don't add blank line for inline data.)
|
|
|
|
|
lines.Add(GenerateBlankLine(offset));
|
2019-12-14 00:52:50 +00:00
|
|
|
|
} else if (mProject.VisualizationSets.ContainsKey(offset) && !addBlank &&
|
|
|
|
|
offset != 0) {
|
|
|
|
|
// Blank line before visualization set helps keep image visually grouped
|
|
|
|
|
// with its data. (Slightly weird things happen with .ORG at the start of
|
|
|
|
|
// the file; don't try to add a blank there.)
|
|
|
|
|
lines.Add(GenerateBlankLine(offset));
|
2019-05-11 17:16:54 +00:00
|
|
|
|
} else if (addBlank) {
|
|
|
|
|
// Previous instruction wanted to be followed by a blank line.
|
|
|
|
|
lines.Add(GenerateBlankLine(offset));
|
|
|
|
|
}
|
|
|
|
|
addBlank = false;
|
|
|
|
|
|
|
|
|
|
// Insert long comments and notes. These may span multiple display lines,
|
|
|
|
|
// and require word-wrap, so it's easiest just to render them fully here.
|
2019-08-17 23:59:08 +00:00
|
|
|
|
// TODO: integrate into FormattedOperandCache so we don't have to
|
|
|
|
|
// regenerate them unless they change. Use the MLC as the dependency.
|
|
|
|
|
if (mProject.Notes.TryGetValue(offset, out MultiLineComment noteData)) {
|
|
|
|
|
List<string> formatted = noteData.FormatText(mFormatter, "NOTE: ");
|
2019-05-11 17:16:54 +00:00
|
|
|
|
StringListToLines(formatted, offset, Line.Type.Note,
|
2019-10-13 00:23:32 +00:00
|
|
|
|
noteData.BackgroundColor, NoteColorMultiplier, lines);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
2019-08-17 23:59:08 +00:00
|
|
|
|
if (mProject.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
|
|
|
|
|
List<string> formatted = longComment.FormatText(mFormatter, string.Empty);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
StringListToLines(formatted, offset, Line.Type.LongComment,
|
2019-10-13 00:23:32 +00:00
|
|
|
|
longComment.BackgroundColor, NoteColorMultiplier, lines);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
2019-11-23 04:45:57 +00:00
|
|
|
|
if (mProject.VisualizationSets.TryGetValue(offset, out VisualizationSet visSet)) {
|
|
|
|
|
lines.Add(new Line(offset, 0, Line.Type.VisualizationSet));
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-08-27 23:45:37 +00:00
|
|
|
|
// Local variable tables come next. Defer rendering.
|
|
|
|
|
if (mProject.LvTables.TryGetValue(offset, out LocalVariableTable lvt)) {
|
2019-08-29 00:34:29 +00:00
|
|
|
|
int count = lvt.Count;
|
2019-08-27 23:45:37 +00:00
|
|
|
|
// If "clear previous" is set, we output an additional line.
|
|
|
|
|
if (lvt.ClearPrevious) {
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
if (count == 0) {
|
|
|
|
|
// Need to show something so the user will know an empty table is here.
|
|
|
|
|
count = 1;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
Line line = new Line(offset, 0, Line.Type.LocalVariableTable, i);
|
|
|
|
|
lines.Add(line);
|
|
|
|
|
}
|
2019-09-25 23:10:07 +00:00
|
|
|
|
lines.Add(GenerateBlankLine(offset));
|
2019-08-27 23:45:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (attr.IsInstructionStart) {
|
2020-07-09 22:17:47 +00:00
|
|
|
|
// Generate reg width directive, if necessary. These are generated after the
|
|
|
|
|
// SEP/REP/PLP instruction has taken effect, so we want it to appear *before*
|
|
|
|
|
// the current instruction.
|
2019-08-17 23:59:08 +00:00
|
|
|
|
if (mProject.CpuDef.HasEmuFlag) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// Changing from "ambiguous but assumed short" to "definitively short"
|
|
|
|
|
// merits a directive, notably at the start of the file. The tricky
|
|
|
|
|
// part is that E=1 means definitively M=1 X=1. And maybe
|
|
|
|
|
// indeterminate E also means that.
|
|
|
|
|
//
|
|
|
|
|
// We don't want to mess with Anattrib, but we do need to tell the
|
|
|
|
|
// assembler something. So we tweak our local copy and propagate it.
|
|
|
|
|
string operandStr = string.Empty;
|
|
|
|
|
StatusFlags curFlags = attr.StatusFlags;
|
2020-07-06 15:27:42 +00:00
|
|
|
|
curFlags.M = attr.StatusFlags.IsShortM ? 1 : 0;
|
|
|
|
|
curFlags.X = attr.StatusFlags.IsShortX ? 1 : 0;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (curFlags.M != prevFlags.M) {
|
|
|
|
|
operandStr = (curFlags.M == 0) ? "longm" : "shortm";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (curFlags.X != prevFlags.X) {
|
|
|
|
|
if (operandStr.Length > 0) {
|
|
|
|
|
operandStr += ",";
|
|
|
|
|
}
|
|
|
|
|
operandStr += (curFlags.X == 0) ? "longx" : "shortx";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (operandStr.Length > 0) {
|
|
|
|
|
Line rwLine = new Line(offset, 0, Line.Type.RegWidthDirective);
|
|
|
|
|
// FormatPseudoOp isn't quite right for the operand, but there
|
|
|
|
|
// isn't anything more suitable, and there are only eight
|
|
|
|
|
// possible values. Having the operand capitalization match the
|
|
|
|
|
// pseudo-op's feels reasonable.
|
|
|
|
|
rwLine.Parts = FormattedParts.CreateDirective(
|
2019-08-17 23:59:08 +00:00
|
|
|
|
mFormatter.FormatPseudoOp(mPseudoOpNames.RegWidthDirective),
|
|
|
|
|
mFormatter.FormatPseudoOp(operandStr));
|
2019-05-11 17:16:54 +00:00
|
|
|
|
lines.Add(rwLine);
|
|
|
|
|
}
|
|
|
|
|
prevFlags = curFlags;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-09 22:17:47 +00:00
|
|
|
|
// Add data bank register change. This is generally applied to a PLB
|
|
|
|
|
// instruction, so we'd like it to appear on the line following the
|
|
|
|
|
// instruction, but that looks funny for e.g. the auto-added B=K at the
|
|
|
|
|
// start of each bank, and would be wrong if placed on a "LDA abs" line.
|
|
|
|
|
// (We could handle PLB lines differently, but apparently inconsistent
|
|
|
|
|
// behavior is unwise.)
|
|
|
|
|
if (mProject.DbrChanges.TryGetValue(offset,
|
|
|
|
|
out CodeAnalysis.DbrValue dbrValue)) {
|
2020-07-10 02:36:22 +00:00
|
|
|
|
string bankStr;
|
2020-07-09 22:17:47 +00:00
|
|
|
|
if (dbrValue.FollowPbr) {
|
2020-07-10 02:36:22 +00:00
|
|
|
|
bankStr = Res.Strings.DATA_BANK_K;
|
2020-07-09 22:17:47 +00:00
|
|
|
|
} else {
|
2020-07-10 02:36:22 +00:00
|
|
|
|
bankStr = mFormatter.FormatHexValue(dbrValue.Bank, 2);
|
2020-07-09 22:17:47 +00:00
|
|
|
|
}
|
2020-07-10 02:36:22 +00:00
|
|
|
|
|
|
|
|
|
string fmt;
|
|
|
|
|
if (dbrValue.ValueSource == CodeAnalysis.DbrValue.Source.Auto) {
|
|
|
|
|
fmt = Res.Strings.DATA_BANK_AUTO_FMT;
|
|
|
|
|
} else {
|
|
|
|
|
fmt = Res.Strings.DATA_BANK_USER_FMT;
|
|
|
|
|
}
|
|
|
|
|
string operandStr = string.Format(fmt, bankStr);
|
|
|
|
|
|
2020-07-09 22:17:47 +00:00
|
|
|
|
Line dbrLine = new Line(offset, 0, Line.Type.DataBankDirective);
|
|
|
|
|
dbrLine.Parts = FormattedParts.CreateDirective(
|
|
|
|
|
mFormatter.FormatPseudoOp(mPseudoOpNames.DataBankDirective),
|
|
|
|
|
mFormatter.FormatPseudoOp(operandStr));
|
|
|
|
|
lines.Add(dbrLine);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// Look for embedded instructions.
|
|
|
|
|
int len;
|
|
|
|
|
for (len = 1; len < attr.Length; len++) {
|
2019-08-17 23:59:08 +00:00
|
|
|
|
if (mProject.GetAnattrib(offset + len).IsInstructionStart) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create Line entry. Offset span only covers the instruction up to
|
|
|
|
|
// the point where the embedded instruction starts.
|
|
|
|
|
Line line = new Line(offset, len, Line.Type.Code);
|
|
|
|
|
lines.Add(line);
|
|
|
|
|
|
|
|
|
|
// Insert blank after an instruction that doesn't continue. Provides a
|
|
|
|
|
// break in code, and before a data area.
|
|
|
|
|
// TODO(maybe): Might also want to do this if the next offset is data,
|
|
|
|
|
// to make things look nicer when code runs directly into data.
|
|
|
|
|
//
|
|
|
|
|
// We don't want to add it with the current line's offset. If we do that,
|
|
|
|
|
// the binary search will get confused, because blank lines have a span
|
|
|
|
|
// of zero. If the code is at offset 10 with length 3, and we search for
|
|
|
|
|
// the byte at offset 11, then a blank line (with span=0) at offset 10 will
|
|
|
|
|
// cause the binary search to assume that the target is farther down, when
|
|
|
|
|
// it's actually one line up. We deal with this by setting a flag and
|
|
|
|
|
// generating the blank line on the next trip through the loop.
|
|
|
|
|
if (attr.DoesNotContinue) {
|
|
|
|
|
addBlank = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offset += len;
|
|
|
|
|
} else {
|
|
|
|
|
Debug.Assert(attr.DataDescriptor != null);
|
2019-08-17 23:59:08 +00:00
|
|
|
|
if (attr.DataDescriptor.IsString) {
|
|
|
|
|
// See if we've already got this one.
|
|
|
|
|
List<string> strLines = mFormattedLineCache.GetStringEntry(offset,
|
|
|
|
|
mFormatter, attr.DataDescriptor, mPseudoOpNames, out string popcode);
|
|
|
|
|
if (strLines == null) {
|
|
|
|
|
//Debug.WriteLine("FMT string at +" + offset.ToString("x6"));
|
|
|
|
|
strLines = PseudoOp.FormatStringOp(mFormatter, mPseudoOpNames,
|
|
|
|
|
attr.DataDescriptor, mProject.FileData, offset,
|
|
|
|
|
out popcode);
|
|
|
|
|
mFormattedLineCache.SetStringEntry(offset, strLines, popcode,
|
|
|
|
|
mFormatter, attr.DataDescriptor, mPseudoOpNames);
|
|
|
|
|
}
|
|
|
|
|
FormattedParts[] partsArray = GenerateStringLines(offset,
|
|
|
|
|
popcode, strLines);
|
|
|
|
|
for (int i = 0; i < strLines.Count; i++) {
|
|
|
|
|
Line line = new Line(offset, attr.Length, Line.Type.Data, i);
|
|
|
|
|
line.Parts = partsArray[i];
|
|
|
|
|
lines.Add(line);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
int numLines = PseudoOp.ComputeRequiredLineCount(mFormatter,
|
|
|
|
|
mPseudoOpNames,attr.DataDescriptor, mProject.FileData, offset);
|
|
|
|
|
for (int i = 0; i < numLines; i++) {
|
|
|
|
|
Line line = new Line(offset, attr.Length, Line.Type.Data, i);
|
|
|
|
|
lines.Add(line);
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
offset += attr.Length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-25 00:35:26 +00:00
|
|
|
|
// See if there were any address shifts in this section. If so, go back and
|
|
|
|
|
// insert an ORG statement as the first entry for the offset. We're expecting to
|
|
|
|
|
// have very few AddressMap entries (usually just one), so it's more efficient to
|
|
|
|
|
// process them here and walk through the sub-list than it is to ping the address map
|
2019-05-11 17:16:54 +00:00
|
|
|
|
// at every line.
|
|
|
|
|
//
|
|
|
|
|
// It should not be possible for an address map change to appear in the middle
|
|
|
|
|
// of an instruction or data item.
|
2019-08-17 23:59:08 +00:00
|
|
|
|
foreach (AddressMap.AddressMapEntry ent in mProject.AddrMap) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (ent.Offset < startOffset || ent.Offset > endOffset) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
int index = FindLineByOffset(lines, ent.Offset);
|
|
|
|
|
if (index < 0) {
|
|
|
|
|
Debug.WriteLine("Couldn't find offset " + ent.Offset +
|
|
|
|
|
" in range we just generated");
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (lines[index].LineType == Line.Type.Blank) {
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
Line topLine = lines[index];
|
|
|
|
|
Line newLine = new Line(topLine.FileOffset, 0, Line.Type.OrgDirective);
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string addrStr = mFormatter.FormatHexValue(ent.Addr, 4);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
newLine.Parts = FormattedParts.CreateDirective(
|
2019-08-17 23:59:08 +00:00
|
|
|
|
mFormatter.FormatPseudoOp(mPseudoOpNames.OrgDirective), addrStr);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
lines.Insert(index, newLine);
|
|
|
|
|
|
|
|
|
|
// Prepend a blank line if the previous line wasn't already blank, and this
|
|
|
|
|
// isn't the ORG at the start of the file. (This may temporarily do
|
|
|
|
|
// double-spacing if we do a partial update, because we won't be able to
|
|
|
|
|
// "see" the previous line. Harmless.)
|
2019-12-28 19:44:26 +00:00
|
|
|
|
// TODO(maybe): consider always adding blanks, and doing a fix-up pass afterward.
|
2019-08-25 21:14:44 +00:00
|
|
|
|
// (but keep in mind that blank lines should always come above things)
|
2019-08-25 00:35:26 +00:00
|
|
|
|
//
|
|
|
|
|
// Interesting case:
|
|
|
|
|
// .dd2 $1000
|
|
|
|
|
// <blank>
|
|
|
|
|
// .org $1234
|
|
|
|
|
// .dd2 $aabb ;comment
|
|
|
|
|
// We need to include "index == 0" or we'll lose the blank when the comment
|
|
|
|
|
// is edited.
|
|
|
|
|
if (ent.Offset != 0 &&
|
|
|
|
|
(index == 0 || (index > 0 && lines[index-1].LineType != Line.Type.Blank))){
|
|
|
|
|
Line blankLine = GenerateBlankLine(topLine.FileOffset);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
lines.Insert(index, blankLine);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generates a blank line entry.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static Line GenerateBlankLine(int offset) {
|
|
|
|
|
return new Line(offset, 0, Line.Type.Blank);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Takes a list of strings and adds them to the Line list as long comments.
|
|
|
|
|
/// </summary>
|
2019-08-17 23:59:08 +00:00
|
|
|
|
/// <param name="list">String list.</param>
|
|
|
|
|
/// <param name="offset">File offset of item start.</param>
|
|
|
|
|
/// <param name="lineType">What type of line this is.</param>
|
|
|
|
|
/// <param name="color">Background color (for Notes).</param>
|
|
|
|
|
/// <param name="lines">Line list to add data to.</param>
|
2019-05-11 17:16:54 +00:00
|
|
|
|
private static void StringListToLines(List<string> list, int offset, Line.Type lineType,
|
2019-10-13 00:23:32 +00:00
|
|
|
|
Color color, float mult, List<Line> lines) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
foreach (string str in list) {
|
|
|
|
|
Line line = new Line(offset, 0, lineType);
|
2019-10-13 00:23:32 +00:00
|
|
|
|
FormattedParts parts = FormattedParts.CreateNote(str,
|
|
|
|
|
Color.Multiply(color, mult));
|
2019-05-11 17:16:54 +00:00
|
|
|
|
line.Parts = parts;
|
|
|
|
|
line.BackgroundColor = color;
|
|
|
|
|
lines.Add(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
private FormattedParts GenerateInstructionLine(int offset, int instrBytes) {
|
|
|
|
|
Anattrib attr = mProject.GetAnattrib(offset);
|
|
|
|
|
byte[] data = mProject.FileData;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string offsetStr = mFormatter.FormatOffset24(offset);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
int addr = attr.Address;
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string addrStr = mFormatter.FormatAddress(addr, !mProject.CpuDef.HasAddr16);
|
|
|
|
|
string bytesStr = mFormatter.FormatBytes(data, offset, instrBytes);
|
|
|
|
|
string flagsStr = attr.StatusFlags.ToString(mProject.CpuDef.HasEmuFlag);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
string attrStr = attr.ToAttrString();
|
|
|
|
|
|
|
|
|
|
string labelStr = string.Empty;
|
|
|
|
|
if (attr.Symbol != null) {
|
2019-11-13 01:24:41 +00:00
|
|
|
|
labelStr = attr.Symbol.GenerateDisplayLabel(mFormatter);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
OpDef op = mProject.CpuDef.GetOpDef(data[offset]);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
int operand = op.GetOperand(data, offset, attr.StatusFlags);
|
|
|
|
|
int instrLen = op.GetLength(attr.StatusFlags);
|
|
|
|
|
OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
|
|
|
|
|
if (op.IsWidthPotentiallyAmbiguous) {
|
|
|
|
|
wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string opcodeStr = mFormatter.FormatOpcode(op, wdis);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (attr.Length != instrBytes) {
|
|
|
|
|
// An instruction is embedded inside this one. Note that BRK is a two-byte
|
|
|
|
|
// instruction, so don't freak out if you see it marked as embedded when a
|
|
|
|
|
// $00 is followed by actual code. (But be a little freaked out that your
|
|
|
|
|
// code is running into a BRK.)
|
|
|
|
|
//opcodeStr = opcodeStr + " \u00bb"; // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
|
|
|
|
|
//opcodeStr = opcodeStr + " \u23e9"; // BLACK RIGHT-POINTING DOUBLE TRIANGLE
|
|
|
|
|
opcodeStr = opcodeStr + " \u25bc"; // BLACK DOWN-POINTING TRIANGLE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string formattedOperand = null;
|
|
|
|
|
int operandLen = instrLen - 1;
|
|
|
|
|
PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None;
|
|
|
|
|
|
|
|
|
|
// Tweak branch instructions. We want to show the absolute address rather
|
|
|
|
|
// than the relative offset (which happens with the OperandAddress assignment
|
|
|
|
|
// below), and 1-byte branches should always appear as a 4-byte hex value.
|
2020-10-11 21:35:17 +00:00
|
|
|
|
if (op.AddrMode == OpDef.AddressMode.PCRel ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.DPPCRel) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
Debug.Assert(attr.OperandAddress >= 0);
|
|
|
|
|
operandLen = 2;
|
|
|
|
|
opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
|
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
|
|
|
|
|
opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
|
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.Imm ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.ImmLongA ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.ImmLongXY) {
|
|
|
|
|
opFlags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
|
|
|
|
|
}
|
2020-07-02 00:59:12 +00:00
|
|
|
|
if (op.IsAbsolutePBR) {
|
|
|
|
|
opFlags |= PseudoOp.FormatNumericOpFlags.IsAbsolutePBR;
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
// Use the OperandAddress when available. This is important for relative branch
|
|
|
|
|
// instructions and PER, where we want to show the target address rather than the
|
|
|
|
|
// operand value.
|
|
|
|
|
int operandForSymbol = operand;
|
|
|
|
|
if (attr.OperandAddress >= 0) {
|
|
|
|
|
operandForSymbol = attr.OperandAddress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check Length to watch for bogus descriptors. ApplyFormatDescriptors() should
|
|
|
|
|
// have discarded anything appropriate, so we might be able to eliminate this test.
|
|
|
|
|
if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) {
|
|
|
|
|
Debug.Assert(operandLen > 0);
|
|
|
|
|
|
|
|
|
|
// Format operand as directed.
|
|
|
|
|
if (op.AddrMode == OpDef.AddressMode.BlockMove) {
|
|
|
|
|
// Special handling for the double-operand block move.
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string opstr1 = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
null, attr.DataDescriptor, operand >> 8, 1,
|
|
|
|
|
PseudoOp.FormatNumericOpFlags.None);
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string opstr2 = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
|
2019-05-11 17:16:54 +00:00
|
|
|
|
null, attr.DataDescriptor, operand & 0xff, 1,
|
|
|
|
|
PseudoOp.FormatNumericOpFlags.None);
|
2019-08-08 20:02:01 +00:00
|
|
|
|
formattedOperand = '#' + opstr1 + "," + '#' + opstr2;
|
2020-10-11 21:35:17 +00:00
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.DPPCRel) {
|
|
|
|
|
// Special handling for double-operand BBR/BBS. The instruction generally
|
|
|
|
|
// behaves like a branch, so format that first.
|
|
|
|
|
string branchStr = PseudoOp.FormatNumericOperand(mFormatter,
|
|
|
|
|
mProject.SymbolTable, mLvLookup, null, attr.DataDescriptor, offset,
|
|
|
|
|
operandForSymbol, operandLen, opFlags);
|
|
|
|
|
string dpStr = mFormatter.FormatHexValue(operand & 0xff, 2);
|
|
|
|
|
formattedOperand = dpStr + "," + branchStr;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
} else {
|
2019-08-31 01:33:05 +00:00
|
|
|
|
formattedOperand = PseudoOp.FormatNumericOperand(mFormatter,
|
|
|
|
|
mProject.SymbolTable, mLvLookup, null, attr.DataDescriptor, offset,
|
|
|
|
|
operandForSymbol, operandLen, opFlags);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Show operand value in hex.
|
|
|
|
|
if (op.AddrMode == OpDef.AddressMode.BlockMove) {
|
2019-08-17 23:59:08 +00:00
|
|
|
|
formattedOperand = '#' + mFormatter.FormatHexValue(operand >> 8, 2) + "," +
|
|
|
|
|
'#' + mFormatter.FormatHexValue(operand & 0xff, 2);
|
2020-10-11 21:35:17 +00:00
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.DPPCRel) {
|
|
|
|
|
formattedOperand = mFormatter.FormatHexValue(operand & 0xff, 2) + "," +
|
|
|
|
|
mFormatter.FormatHexValue(operandForSymbol, operandLen * 2);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
if (operandLen == 2) {
|
|
|
|
|
// This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
|
|
|
|
|
// when outside bank zero. The bank is included in the operand address,
|
|
|
|
|
// but we don't want to show it here.
|
2020-07-04 00:37:04 +00:00
|
|
|
|
// (Some assemblers want the bank to be shown for JSR/JMP, but we don't
|
|
|
|
|
// do that here. See the corresponding code in AsmGen.GenCommon)
|
2019-05-11 17:16:54 +00:00
|
|
|
|
operandForSymbol &= 0xffff;
|
|
|
|
|
}
|
2019-08-17 23:59:08 +00:00
|
|
|
|
formattedOperand = mFormatter.FormatHexValue(operandForSymbol, operandLen * 2);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string operandStr = mFormatter.FormatOperand(op, formattedOperand, wdis);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string eolComment = mProject.Comments[offset];
|
|
|
|
|
if (mShowCycleCounts) {
|
2019-05-11 17:16:54 +00:00
|
|
|
|
bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
|
2019-08-17 23:59:08 +00:00
|
|
|
|
int cycles = mProject.CpuDef.GetCycles(op.Opcode, attr.StatusFlags,
|
|
|
|
|
attr.BranchTaken, branchCross);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (cycles > 0) {
|
2019-09-12 20:57:52 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(eolComment)) {
|
|
|
|
|
eolComment = cycles.ToString() + " " + eolComment;
|
|
|
|
|
} else {
|
|
|
|
|
eolComment = cycles.ToString();
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
} else {
|
2019-09-12 20:57:52 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(eolComment)) {
|
|
|
|
|
eolComment = (-cycles).ToString() + "+ " + eolComment;
|
|
|
|
|
} else {
|
|
|
|
|
eolComment = (-cycles).ToString() + "+";
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-17 23:59:08 +00:00
|
|
|
|
string commentStr = mFormatter.FormatEolComment(eolComment);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
|
2019-05-28 01:46:09 +00:00
|
|
|
|
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr);
|
2019-09-02 22:18:55 +00:00
|
|
|
|
if (mProject.StatusFlagOverrides[offset] != StatusFlags.DefaultValue) {
|
|
|
|
|
parts = FormattedParts.SetFlagsModified(parts);
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
return parts;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
private FormattedParts GenerateDataLine(int offset, int subLineIndex) {
|
|
|
|
|
Anattrib attr = mProject.GetAnattrib(offset);
|
|
|
|
|
byte[] data = mProject.FileData;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
|
|
|
|
string offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr,
|
2019-05-28 01:46:09 +00:00
|
|
|
|
operandStr, commentStr;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
offsetStr = addrStr = bytesStr = flagsStr = attrStr = labelStr = opcodeStr =
|
2019-05-28 01:46:09 +00:00
|
|
|
|
operandStr = commentStr = string.Empty;
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(mFormatter, mPseudoOpNames,
|
|
|
|
|
mProject.SymbolTable, null, attr.DataDescriptor, mProject.FileData, offset,
|
|
|
|
|
subLineIndex);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (subLineIndex == 0) {
|
2019-08-17 23:59:08 +00:00
|
|
|
|
offsetStr = mFormatter.FormatOffset24(offset);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
if (attr.Symbol != null) {
|
2019-11-13 01:24:41 +00:00
|
|
|
|
labelStr = attr.Symbol.GenerateDisplayLabel(mFormatter);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
bytesStr = mFormatter.FormatBytes(data, offset, attr.Length);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
attrStr = attr.ToAttrString();
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
opcodeStr = mFormatter.FormatPseudoOp(pout.Opcode);
|
|
|
|
|
commentStr = mFormatter.FormatEolComment(mProject.Comments[offset]);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
opcodeStr = " +";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
operandStr = pout.Operand;
|
|
|
|
|
|
2019-10-23 20:00:46 +00:00
|
|
|
|
// This seems less useful in practice than in theory, unless you have a lot of
|
|
|
|
|
// character data separated by unprintable values. Text generally gets found by
|
|
|
|
|
// the data scanner, and having character values next to things that aren't
|
|
|
|
|
// actually meant to be character data is more distracting than useful. If
|
|
|
|
|
// there's a good use case we could make it an option, and have mCharTest set
|
|
|
|
|
// to null if it's switched off.
|
|
|
|
|
if (false && attr.DataDescriptor.FormatType == FormatDescriptor.Type.Default) {
|
|
|
|
|
FormatDescriptor dfd = attr.DataDescriptor;
|
|
|
|
|
Debug.Assert(dfd.Length == 1);
|
|
|
|
|
int operand = RawData.GetWord(mProject.FileData, offset, dfd.Length, false);
|
|
|
|
|
|
|
|
|
|
if (mCharTest((byte)operand)) {
|
|
|
|
|
operandStr += " '" + mCharConv((byte)operand) + "'";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 17:16:54 +00:00
|
|
|
|
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
|
2019-05-28 01:46:09 +00:00
|
|
|
|
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr);
|
2019-05-11 17:16:54 +00:00
|
|
|
|
return parts;
|
|
|
|
|
}
|
2019-08-17 23:59:08 +00:00
|
|
|
|
|
2019-08-27 23:45:37 +00:00
|
|
|
|
private FormattedParts GenerateLvTableLine(int offset, int subLineIndex) {
|
|
|
|
|
if (!mProject.LvTables.TryGetValue(offset, out LocalVariableTable lvt)) {
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
return FormattedParts.CreateLongComment("BAD OFFSET +" + offset.ToString("x6") +
|
|
|
|
|
" sub=" + subLineIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lvt.ClearPrevious) {
|
|
|
|
|
if (subLineIndex == 0) {
|
|
|
|
|
return FormattedParts.CreateLongComment(
|
|
|
|
|
Res.Strings.LOCAL_VARIABLE_TABLE_CLEAR);
|
|
|
|
|
} else {
|
|
|
|
|
// adjust for previously output "clear" line
|
|
|
|
|
subLineIndex--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 00:34:29 +00:00
|
|
|
|
if (lvt.Count == 0) {
|
2019-08-27 23:45:37 +00:00
|
|
|
|
// If ClearPrevious is set, we returned the "clear" line for index zero.
|
|
|
|
|
// So this is an empty table without a clear. We want to show something so
|
|
|
|
|
// the user knows there's dead weight here.
|
|
|
|
|
Debug.Assert(subLineIndex == 0 && !lvt.ClearPrevious);
|
|
|
|
|
return FormattedParts.CreateLongComment(
|
|
|
|
|
Res.Strings.LOCAL_VARIABLE_TABLE_EMPTY);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 00:34:29 +00:00
|
|
|
|
if (subLineIndex >= lvt.Count) {
|
2019-08-27 23:45:37 +00:00
|
|
|
|
return FormattedParts.CreateLongComment("BAD INDEX +" + offset.ToString("x6") +
|
|
|
|
|
" sub=" + subLineIndex);
|
|
|
|
|
} else {
|
Fix various local variable de-duplication bugs
In 1.5.0-dev1, as part of changes to the way label localization
works, the local variable de-duplicator started checking against a
filtered copy of the symbol table. Unfortunately it never
re-generated the table, so a long-lived LocalVariableLookup (like
the one used by LineListGen) would set up the dup map wrong and
be inconsistent with other parts of the program.
We now regenerate the table on every Reset().
The de-duplication stuff also had problems when opcodes and
operands were double-clicked on. When the opcode is clicked, the
selection should jump to the appropriate variable declaration, but
it wasn't being found because the label generated in the list was
in its original form. Fixed.
When an instruction operand is double-clicked, the instruction operand
editor opens with an "edit variable" shortcut. This was showing
the de-duplicated name, which isn't necessarily a bad thing, but it
was passing that value on to the DefSymbol editor, which thought it
was being asked to create a new entry. Fixed. (Entering the editor
through the LvTable editor works correctly, with nary a de-duplicated
name in sight. You'll be forced to rename it because it'll fail the
uniqueness test.)
References to de-duplicated local variables were getting lost when
the symbol's label was replaced (due largely to a convenient but
flawed shortcut: xrefs are attached to DefSymbol objects). Fixed by
linking the XrefSets.
Given the many issues and their relative subtlety, I decided to make
the modified names more obvious, and went back to the "_DUPn" naming
strategy. (I'm also considering just making it an error and
discarding conflicting entries during analysis... this is much more
complicated than I expected it to be.)
Quick tests can be performed in 2019-local-variables:
- go to +000026, double-click on the opcode, confirm sel change
- go to +000026, double-click on the operand, confirm orig name
shown in shortcut and that shortcut opens editor with orig name
- go to +00001a, down a line, click on PROJ_ZERO_DUP1 and confirm
that it has a single reference (from +000026)
- double-click on var table and confirm editing entry
2020-01-14 01:54:47 +00:00
|
|
|
|
// Getting the symbol directly from the LvTable yields the original form,
|
|
|
|
|
// but we want the de-dup form.
|
|
|
|
|
//DefSymbol defSym = lvt[subLineIndex];
|
|
|
|
|
List<DefSymbol> lvars = mLvLookup.GetVariablesDefinedAtOffset(offset);
|
|
|
|
|
DefSymbol defSym = lvars[subLineIndex];
|
|
|
|
|
|
|
|
|
|
// Use an operand length of 1 so the value is shown as concisely as possible.
|
2019-08-27 23:45:37 +00:00
|
|
|
|
string addrStr = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
|
|
|
|
|
null, defSym.DataDescriptor, defSym.Value, 1,
|
|
|
|
|
PseudoOp.FormatNumericOpFlags.None);
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
addrStr = PseudoOp.AnnotateEquDirective(mFormatter, addrStr, defSym);
|
2019-08-27 23:45:37 +00:00
|
|
|
|
string comment = mFormatter.FormatEolComment(defSym.Comment);
|
2019-08-31 01:33:05 +00:00
|
|
|
|
return FormattedParts.CreateEquDirective(
|
2019-11-13 01:24:41 +00:00
|
|
|
|
mFormatter.FormatVariableLabel(defSym.GenerateDisplayLabel(mFormatter)),
|
2019-08-29 19:14:47 +00:00
|
|
|
|
mFormatter.FormatPseudoOp(mPseudoOpNames.VarDirective),
|
2019-08-27 23:45:37 +00:00
|
|
|
|
addrStr, comment);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 20:32:45 +00:00
|
|
|
|
public DefSymbol GetLocalVariableFromLine(int lineIndex) {
|
|
|
|
|
Line line = this[lineIndex];
|
|
|
|
|
int offset = line.FileOffset;
|
|
|
|
|
if (!mProject.LvTables.TryGetValue(offset, out LocalVariableTable lvt)) {
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
int tableIndex = line.SubLineIndex;
|
|
|
|
|
if (lvt.ClearPrevious) {
|
2019-08-29 22:20:38 +00:00
|
|
|
|
tableIndex--;
|
|
|
|
|
}
|
|
|
|
|
if (tableIndex < 0 || tableIndex >= lvt.Count) {
|
|
|
|
|
// Will be -1 on first line when ClearPrevious was set. Will be zero on
|
|
|
|
|
// first line of empty table.
|
|
|
|
|
return null;
|
2019-08-29 20:32:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
Fix various local variable de-duplication bugs
In 1.5.0-dev1, as part of changes to the way label localization
works, the local variable de-duplicator started checking against a
filtered copy of the symbol table. Unfortunately it never
re-generated the table, so a long-lived LocalVariableLookup (like
the one used by LineListGen) would set up the dup map wrong and
be inconsistent with other parts of the program.
We now regenerate the table on every Reset().
The de-duplication stuff also had problems when opcodes and
operands were double-clicked on. When the opcode is clicked, the
selection should jump to the appropriate variable declaration, but
it wasn't being found because the label generated in the list was
in its original form. Fixed.
When an instruction operand is double-clicked, the instruction operand
editor opens with an "edit variable" shortcut. This was showing
the de-duplicated name, which isn't necessarily a bad thing, but it
was passing that value on to the DefSymbol editor, which thought it
was being asked to create a new entry. Fixed. (Entering the editor
through the LvTable editor works correctly, with nary a de-duplicated
name in sight. You'll be forced to rename it because it'll fail the
uniqueness test.)
References to de-duplicated local variables were getting lost when
the symbol's label was replaced (due largely to a convenient but
flawed shortcut: xrefs are attached to DefSymbol objects). Fixed by
linking the XrefSets.
Given the many issues and their relative subtlety, I decided to make
the modified names more obvious, and went back to the "_DUPn" naming
strategy. (I'm also considering just making it an error and
discarding conflicting entries during analysis... this is much more
complicated than I expected it to be.)
Quick tests can be performed in 2019-local-variables:
- go to +000026, double-click on the opcode, confirm sel change
- go to +000026, double-click on the operand, confirm orig name
shown in shortcut and that shortcut opens editor with orig name
- go to +00001a, down a line, click on PROJ_ZERO_DUP1 and confirm
that it has a single reference (from +000026)
- double-click on var table and confirm editing entry
2020-01-14 01:54:47 +00:00
|
|
|
|
// Go through LvLookup to get de-dup form of labels.
|
|
|
|
|
//return lvt[tableIndex];
|
|
|
|
|
List<DefSymbol> lvars = mLvLookup.GetVariablesDefinedAtOffset(offset);
|
|
|
|
|
return lvars[tableIndex];
|
2019-08-29 20:32:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-17 23:59:08 +00:00
|
|
|
|
private FormattedParts[] GenerateStringLines(int offset, string popcode,
|
|
|
|
|
List<string> operands) {
|
|
|
|
|
FormattedParts[] partsArray = new FormattedParts[operands.Count];
|
|
|
|
|
|
|
|
|
|
Anattrib attr = mProject.GetAnattrib(offset);
|
|
|
|
|
byte[] data = mProject.FileData;
|
|
|
|
|
|
|
|
|
|
string offsetStr, addrStr, bytesStr, attrStr, labelStr, opcodeStr,
|
|
|
|
|
operandStr, commentStr;
|
|
|
|
|
|
|
|
|
|
for (int subLineIndex = 0; subLineIndex < operands.Count; subLineIndex++) {
|
|
|
|
|
if (subLineIndex == 0) {
|
|
|
|
|
offsetStr = mFormatter.FormatOffset24(offset);
|
|
|
|
|
|
|
|
|
|
addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16);
|
|
|
|
|
if (attr.Symbol != null) {
|
2019-11-13 01:24:41 +00:00
|
|
|
|
labelStr = attr.Symbol.GenerateDisplayLabel(mFormatter);
|
2019-08-17 23:59:08 +00:00
|
|
|
|
} else {
|
|
|
|
|
labelStr = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytesStr = mFormatter.FormatBytes(data, offset, attr.Length);
|
|
|
|
|
attrStr = attr.ToAttrString();
|
|
|
|
|
|
|
|
|
|
opcodeStr = mFormatter.FormatPseudoOp(popcode);
|
|
|
|
|
commentStr = mFormatter.FormatEolComment(mProject.Comments[offset]);
|
|
|
|
|
} else {
|
|
|
|
|
offsetStr = addrStr = bytesStr = attrStr = labelStr = commentStr =
|
|
|
|
|
string.Empty;
|
|
|
|
|
|
|
|
|
|
opcodeStr = " +";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
operandStr = operands[subLineIndex];
|
|
|
|
|
|
|
|
|
|
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
|
|
|
|
|
/*flags*/string.Empty, attrStr, labelStr, opcodeStr, operandStr, commentStr);
|
|
|
|
|
|
|
|
|
|
partsArray[subLineIndex] = parts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return partsArray;
|
|
|
|
|
}
|
2019-05-11 17:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|