diff --git a/SourceGen/FormattedOperandCache.cs b/SourceGen/FormattedOperandCache.cs
new file mode 100644
index 0000000..13abc6b
--- /dev/null
+++ b/SourceGen/FormattedOperandCache.cs
@@ -0,0 +1,141 @@
+/*
+ * 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 Asm65;
+
+namespace SourceGen {
+ ///
+ /// Holds a cache of formatted lines.
+ ///
+ ///
+ /// This is intended for multi-line items with lengths that are non-trivial to compute,
+ /// such as long comments (which have to do word wrapping) and strings (which may
+ /// be a mix of characters and hex data). The on-demand line formatter needs to be able
+ /// to render the Nth line of a multi-line string, and will potentially be very
+ /// inefficient if it has to render lies 0 through N-1 as well. (Imagine the list is
+ /// rendered from end to start...) Single-line items, and multi-line items that are
+ /// easy to generate at an arbitrary offset (dense hex), aren't stored here.
+ ///
+ /// The trick is knowing when the cached data must be invalidated. For example, a
+ /// fully formatted string line must be invalidated if:
+ /// - The Formatter changes (different delimiter definition)
+ /// - The FormatDescriptor changes (different length, different text encoding, different
+ /// type of string)
+ /// - The PseudoOpNames table changes (potentially altering the pseudo-op string used)
+ ///
+ /// Doing a full .equals() on the various items would reduce performance, so we use a
+ /// simple test on reference equality when possible, and expect that the client will try
+ /// to ensure that the various bits that are depended upon don't get replaced unnecessarily.
+ ///
+ public class FormattedOperandCache {
+ private const bool VERBOSE = false;
+
+ private class FormattedStringEntry {
+ public List Lines { get; private set; }
+ public string PseudoOpcode { get; private set; }
+
+ private Formatter mFormatter;
+ private FormatDescriptor mFormatDescriptor;
+ private PseudoOp.PseudoOpNames mPseudoOpNames;
+
+ public FormattedStringEntry(List lines, string popcode, Formatter formatter,
+ FormatDescriptor formatDescriptor, PseudoOp.PseudoOpNames pseudoOpNames) {
+ // Can't be sure the list won't change, so duplicate it.
+ Lines = new List(lines.Count);
+ foreach (string str in lines) {
+ Lines.Add(str);
+ }
+ PseudoOpcode = popcode;
+
+ mFormatter = formatter;
+ mFormatDescriptor = formatDescriptor;
+ mPseudoOpNames = pseudoOpNames;
+ }
+
+ ///
+ /// Checks the entry's dependencies.
+ ///
+ ///
+ /// The data analyzer regenerates stuff in Anattribs, so we can't expect to have
+ /// the same FormatDescriptor object.
+ ///
+ /// True if the dependencies match.
+ public bool CheckDeps(Formatter formatter, FormatDescriptor formatDescriptor,
+ PseudoOp.PseudoOpNames pseudoOpNames) {
+ bool ok = (ReferenceEquals(mFormatter, formatter) &&
+ ReferenceEquals(mPseudoOpNames, pseudoOpNames) &&
+ mFormatDescriptor == formatDescriptor);
+ //if (!ok) {
+ // Debug.WriteLine("CheckDeps:" +
+ // (ReferenceEquals(mFormatter, formatter) ? "" : " fmt") +
+ // (ReferenceEquals(mPseudoOpNames, pseudoOpNames) ? "" : " pop") +
+ // (mFormatDescriptor == formatDescriptor ? "" : " dfd"));
+ //}
+ return ok;
+ }
+ }
+
+ private Dictionary mStringEntries =
+ new Dictionary();
+
+
+ ///
+ /// Retrieves the formatted string data for the specified offset.
+ ///
+ /// File offset.
+ /// Formatter dependency.
+ /// FormatDescriptor dependency.
+ /// PseudoOpNames dependency.
+ /// Pseudo-op for this string.
+ /// A reference to the string list. The caller must not modify the
+ /// list.
+ public List GetStringEntry(int offset, Formatter formatter,
+ FormatDescriptor formatDescriptor, PseudoOp.PseudoOpNames pseudoOpNames,
+ out string PseudoOpcode) {
+ PseudoOpcode = null;
+ if (!mStringEntries.TryGetValue(offset, out FormattedStringEntry entry)) {
+ return null;
+ }
+ if (!entry.CheckDeps(formatter, formatDescriptor, pseudoOpNames)) {
+ //Debug.WriteLine(" stale entry at +" + offset.ToString("x6"));
+ return null;
+ }
+ PseudoOpcode = entry.PseudoOpcode;
+ return entry.Lines;
+ }
+
+ ///
+ /// Sets the string data entry for the specified offset.
+ ///
+ /// File offset.
+ /// String data.
+ /// Pseudo-opcode for this line.
+ /// Formatter dependency.
+ /// FormatDescriptor dependency.
+ /// PseudoOpNames dependency.
+ public void SetStringEntry(int offset, List lines, string pseudoOpcode,
+ Formatter formatter, FormatDescriptor formatDescriptor,
+ PseudoOp.PseudoOpNames pseudoOpNames) {
+ Debug.Assert(lines != null);
+ FormattedStringEntry fse = new FormattedStringEntry(lines, pseudoOpcode,
+ formatter, formatDescriptor, pseudoOpNames);
+ mStringEntries[offset] = fse;
+ }
+ }
+}
diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs
index f195304..cbe0860 100644
--- a/SourceGen/LineListGen.cs
+++ b/SourceGen/LineListGen.cs
@@ -63,6 +63,12 @@ namespace SourceGen {
///
private PseudoOp.PseudoOpNames mPseudoOpNames;
+ ///
+ /// Cache of previously-formatted data. The data is stored with references to
+ /// dependencies, so it should not be necessary to explicitly clear this.
+ ///
+ private FormattedOperandCache mFormattedLineCache;
+
///
/// One of these per line of output in the display. It should be possible to draw
@@ -421,6 +427,7 @@ namespace SourceGen {
mPseudoOpNames = opNames;
mLineList = new List();
+ mFormattedLineCache = new FormattedOperandCache();
mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS,
false);
@@ -481,12 +488,10 @@ namespace SourceGen {
FormattedParts parts;
switch (line.LineType) {
case Line.Type.Code:
- parts = GenerateInstructionLine(mProject, mFormatter,
- line.FileOffset, line.OffsetSpan, mShowCycleCounts);
+ parts = GenerateInstructionLine(line.FileOffset, line.OffsetSpan);
break;
case Line.Type.Data:
- parts = GenerateDataLine(mProject, mFormatter, mPseudoOpNames,
- line.FileOffset, line.SubLineIndex);
+ parts = GenerateDataLine(line.FileOffset, line.SubLineIndex);
break;
case Line.Type.Blank:
// Nothing to do.
@@ -624,8 +629,7 @@ namespace SourceGen {
List headerLines = GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames);
mLineList.InsertRange(0, headerLines);
- GenerateLineList(mProject, mFormatter, mPseudoOpNames,
- mProject.FileData, 0, mProject.FileData.Length - 1, mLineList);
+ GenerateLineList(0, mProject.FileData.Length - 1, mLineList);
mDisplayList.ResetList(mLineList.Count);
@@ -709,8 +713,7 @@ namespace SourceGen {
// 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 newLines = new List(endIndex - startIndex + 1);
- GenerateLineList(mProject, mFormatter, mPseudoOpNames, mProject.FileData,
- startOffset, endOffset, newLines);
+ GenerateLineList(startOffset, endOffset, newLines);
// Out with the old, in with the new.
mLineList.RemoveRange(startIndex, endIndex - startIndex + 1);
@@ -862,14 +865,10 @@ namespace SourceGen {
/// options, like maximum per-line operand length, might affect how many lines
/// are generated.
///
- /// Project reference.
- /// Output formatter.
/// Offset of first byte.
/// Offset of last byte.
/// List to add output lines to.
- private static void GenerateLineList(DisasmProject proj, Formatter formatter,
- PseudoOp.PseudoOpNames opNames, byte[] data, int startOffset, int endOffset,
- List lines) {
+ private void GenerateLineList(int startOffset, int endOffset, List lines) {
//Debug.WriteLine("GenerateRange [+" + startOffset.ToString("x6") + ",+" +
// endOffset.ToString("x6") + "]");
@@ -878,9 +877,9 @@ namespace SourceGen {
// Find the previous status flags for M/X tracking.
StatusFlags prevFlags = StatusFlags.AllIndeterminate;
- if (proj.CpuDef.HasEmuFlag) {
+ if (mProject.CpuDef.HasEmuFlag) {
for (int scanoff = startOffset - 1; scanoff >= 0; scanoff--) {
- Anattrib attr = proj.GetAnattrib(scanoff);
+ Anattrib attr = mProject.GetAnattrib(scanoff);
if (attr.IsInstructionStart) {
prevFlags = attr.StatusFlags;
// Apply the same tweak here that we do to curFlags below.
@@ -900,17 +899,16 @@ namespace SourceGen {
// partial-list generation.
bool addBlank = false;
if (startOffset > 0) {
- int baseOff = DataAnalysis.GetBaseOperandOffset(proj, startOffset - 1);
- if (proj.GetAnattrib(baseOff).DoesNotContinue) {
+ int baseOff = DataAnalysis.GetBaseOperandOffset(mProject, startOffset - 1);
+ if (mProject.GetAnattrib(baseOff).DoesNotContinue) {
addBlank = true;
}
}
-
int offset = startOffset;
while (offset <= endOffset) {
- Anattrib attr = proj.GetAnattrib(offset);
+ Anattrib attr = mProject.GetAnattrib(offset);
if (attr.IsInstructionStart && offset > 0 &&
- proj.GetAnattrib(offset - 1).IsData) {
+ mProject.GetAnattrib(offset - 1).IsData) {
// Transition from data to code. (Don't add blank line for inline data.)
lines.Add(GenerateBlankLine(offset));
} else if (addBlank) {
@@ -921,20 +919,22 @@ namespace SourceGen {
// 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.
- if (proj.Notes.TryGetValue(offset, out MultiLineComment noteData)) {
- List formatted = noteData.FormatText(formatter, "NOTE: ");
+ // 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 formatted = noteData.FormatText(mFormatter, "NOTE: ");
StringListToLines(formatted, offset, Line.Type.Note,
noteData.BackgroundColor, lines);
}
- if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
- List formatted = longComment.FormatText(formatter, string.Empty);
+ if (mProject.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
+ List formatted = longComment.FormatText(mFormatter, string.Empty);
StringListToLines(formatted, offset, Line.Type.LongComment,
longComment.BackgroundColor, lines);
}
if (attr.IsInstructionStart) {
// Generate reg width directive, if necessary.
- if (proj.CpuDef.HasEmuFlag) {
+ if (mProject.CpuDef.HasEmuFlag) {
// 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
@@ -964,8 +964,8 @@ namespace SourceGen {
// possible values. Having the operand capitalization match the
// pseudo-op's feels reasonable.
rwLine.Parts = FormattedParts.CreateDirective(
- formatter.FormatPseudoOp(opNames.RegWidthDirective),
- formatter.FormatPseudoOp(operandStr));
+ mFormatter.FormatPseudoOp(mPseudoOpNames.RegWidthDirective),
+ mFormatter.FormatPseudoOp(operandStr));
lines.Add(rwLine);
}
prevFlags = curFlags;
@@ -974,7 +974,7 @@ namespace SourceGen {
// Look for embedded instructions.
int len;
for (len = 1; len < attr.Length; len++) {
- if (proj.GetAnattrib(offset + len).IsInstructionStart) {
+ if (mProject.GetAnattrib(offset + len).IsInstructionStart) {
break;
}
}
@@ -1003,15 +1003,32 @@ namespace SourceGen {
offset += len;
} else {
Debug.Assert(attr.DataDescriptor != null);
- // TODO: replace this with something that caches expensive items like
- // string operands; maybe have an out List that is null for the
- // easy stuff
- int numLines =
- PseudoOp.ComputeRequiredLineCount(formatter, opNames, attr.DataDescriptor,
- data, offset);
- for (int i = 0; i < numLines; i++) {
- Line line = new Line(offset, attr.Length, Line.Type.Data, i);
- lines.Add(line);
+ if (attr.DataDescriptor.IsString) {
+ // See if we've already got this one.
+ List 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);
+ }
}
offset += attr.Length;
}
@@ -1025,7 +1042,7 @@ namespace SourceGen {
//
// It should not be possible for an address map change to appear in the middle
// of an instruction or data item.
- foreach (AddressMap.AddressMapEntry ent in proj.AddrMap) {
+ foreach (AddressMap.AddressMapEntry ent in mProject.AddrMap) {
if (ent.Offset < startOffset || ent.Offset > endOffset) {
continue;
}
@@ -1041,9 +1058,9 @@ namespace SourceGen {
}
Line topLine = lines[index];
Line newLine = new Line(topLine.FileOffset, 0, Line.Type.OrgDirective);
- string addrStr = formatter.FormatHexValue(ent.Addr, 4);
+ string addrStr = mFormatter.FormatHexValue(ent.Addr, 4);
newLine.Parts = FormattedParts.CreateDirective(
- formatter.FormatPseudoOp(opNames.OrgDirective), addrStr);
+ mFormatter.FormatPseudoOp(mPseudoOpNames.OrgDirective), addrStr);
lines.Insert(index, newLine);
// Prepend a blank line if the previous line wasn't already blank, and this
@@ -1067,11 +1084,11 @@ namespace SourceGen {
///
/// Takes a list of strings and adds them to the Line list as long comments.
///
- ///
- ///
- ///
- ///
- ///
+ /// String list.
+ /// File offset of item start.
+ /// What type of line this is.
+ /// Background color (for Notes).
+ /// Line list to add data to.
private static void StringListToLines(List list, int offset, Line.Type lineType,
Color color, List lines) {
foreach (string str in list) {
@@ -1083,17 +1100,16 @@ namespace SourceGen {
}
}
- private static FormattedParts GenerateInstructionLine(DisasmProject proj,
- Formatter formatter, int offset, int instrBytes, bool showCycleCounts) {
- Anattrib attr = proj.GetAnattrib(offset);
- byte[] data = proj.FileData;
+ private FormattedParts GenerateInstructionLine(int offset, int instrBytes) {
+ Anattrib attr = mProject.GetAnattrib(offset);
+ byte[] data = mProject.FileData;
- string offsetStr = formatter.FormatOffset24(offset);
+ string offsetStr = mFormatter.FormatOffset24(offset);
int addr = attr.Address;
- string addrStr = formatter.FormatAddress(addr, !proj.CpuDef.HasAddr16);
- string bytesStr = formatter.FormatBytes(data, offset, instrBytes);
- string flagsStr = attr.StatusFlags.ToString(proj.CpuDef.HasEmuFlag);
+ string addrStr = mFormatter.FormatAddress(addr, !mProject.CpuDef.HasAddr16);
+ string bytesStr = mFormatter.FormatBytes(data, offset, instrBytes);
+ string flagsStr = attr.StatusFlags.ToString(mProject.CpuDef.HasEmuFlag);
string attrStr = attr.ToAttrString();
string labelStr = string.Empty;
@@ -1101,7 +1117,7 @@ namespace SourceGen {
labelStr = attr.Symbol.Label;
}
- OpDef op = proj.CpuDef.GetOpDef(data[offset]);
+ OpDef op = mProject.CpuDef.GetOpDef(data[offset]);
int operand = op.GetOperand(data, offset, attr.StatusFlags);
int instrLen = op.GetLength(attr.StatusFlags);
OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
@@ -1109,7 +1125,7 @@ namespace SourceGen {
wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
}
- string opcodeStr = formatter.FormatOpcode(op, wdis);
+ string opcodeStr = mFormatter.FormatOpcode(op, wdis);
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
@@ -1156,22 +1172,22 @@ namespace SourceGen {
// Format operand as directed.
if (op.AddrMode == OpDef.AddressMode.BlockMove) {
// Special handling for the double-operand block move.
- string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ string opstr1 = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
null, attr.DataDescriptor, operand >> 8, 1,
PseudoOp.FormatNumericOpFlags.None);
- string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ string opstr2 = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
null, attr.DataDescriptor, operand & 0xff, 1,
PseudoOp.FormatNumericOpFlags.None);
formattedOperand = '#' + opstr1 + "," + '#' + opstr2;
} else {
- formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ formattedOperand = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
null, attr.DataDescriptor, operandForSymbol, operandLen, opFlags);
}
} else {
// Show operand value in hex.
if (op.AddrMode == OpDef.AddressMode.BlockMove) {
- formattedOperand = '#' + formatter.FormatHexValue(operand >> 8, 2) + "," +
- '#' + formatter.FormatHexValue(operand & 0xff, 2);
+ formattedOperand = '#' + mFormatter.FormatHexValue(operand >> 8, 2) + "," +
+ '#' + mFormatter.FormatHexValue(operand & 0xff, 2);
} else {
if (operandLen == 2) {
// This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
@@ -1179,66 +1195,107 @@ namespace SourceGen {
// but we don't want to show it here.
operandForSymbol &= 0xffff;
}
- formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
+ formattedOperand = mFormatter.FormatHexValue(operandForSymbol, operandLen * 2);
}
}
- string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);
+ string operandStr = mFormatter.FormatOperand(op, formattedOperand, wdis);
- string eolComment = proj.Comments[offset];
- if (showCycleCounts) {
+ string eolComment = mProject.Comments[offset];
+ if (mShowCycleCounts) {
bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
- int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
- branchCross);
+ int cycles = mProject.CpuDef.GetCycles(op.Opcode, attr.StatusFlags,
+ attr.BranchTaken, branchCross);
if (cycles > 0) {
eolComment = cycles.ToString() + " " + eolComment;
} else {
eolComment = (-cycles).ToString() + "+ " + eolComment;
}
}
- string commentStr = formatter.FormatEolComment(eolComment);
+ string commentStr = mFormatter.FormatEolComment(eolComment);
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr);
return parts;
}
- private static FormattedParts GenerateDataLine(DisasmProject proj, Formatter formatter,
- PseudoOp.PseudoOpNames opNames, int offset, int subLineIndex) {
- Anattrib attr = proj.GetAnattrib(offset);
- byte[] data = proj.FileData;
+ private FormattedParts GenerateDataLine(int offset, int subLineIndex) {
+ Anattrib attr = mProject.GetAnattrib(offset);
+ byte[] data = mProject.FileData;
string offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr,
operandStr, commentStr;
offsetStr = addrStr = bytesStr = flagsStr = attrStr = labelStr = opcodeStr =
operandStr = commentStr = string.Empty;
- PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(formatter, opNames, proj.SymbolTable,
- null, attr.DataDescriptor, proj.FileData, offset, subLineIndex);
+ PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(mFormatter, mPseudoOpNames,
+ mProject.SymbolTable, null, attr.DataDescriptor, mProject.FileData, offset,
+ subLineIndex);
if (subLineIndex == 0) {
- offsetStr = formatter.FormatOffset24(offset);
+ offsetStr = mFormatter.FormatOffset24(offset);
- addrStr = formatter.FormatAddress(attr.Address, !proj.CpuDef.HasAddr16);
+ addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16);
if (attr.Symbol != null) {
labelStr = attr.Symbol.Label;
}
- bytesStr = formatter.FormatBytes(data, offset, attr.Length);
+ bytesStr = mFormatter.FormatBytes(data, offset, attr.Length);
attrStr = attr.ToAttrString();
- opcodeStr = formatter.FormatPseudoOp(pout.Opcode);
+ opcodeStr = mFormatter.FormatPseudoOp(pout.Opcode);
+ commentStr = mFormatter.FormatEolComment(mProject.Comments[offset]);
} else {
opcodeStr = " +";
}
operandStr = pout.Operand;
- if (subLineIndex == 0) {
- commentStr = formatter.FormatEolComment(proj.Comments[offset]);
- }
-
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr);
return parts;
}
+
+ private FormattedParts[] GenerateStringLines(int offset, string popcode,
+ List 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) {
+ labelStr = attr.Symbol.Label;
+ } 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;
+ }
}
}
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index 0336749..284955f 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -2462,30 +2462,37 @@ namespace SourceGen {
// A single line can span multiple offsets, each of which could have a
// different hint. Note the code/data hints are only applied to the first
// byte of each selected line, so we're not quite in sync with that.
- for (int offset = line.FileOffset; offset < line.FileOffset + line.OffsetSpan;
- offset++) {
- switch (mProject.TypeHints[offset]) {
- case CodeAnalysis.TypeHint.Code:
- codeHints++;
- break;
- case CodeAnalysis.TypeHint.Data:
- dataHints++;
- break;
- case CodeAnalysis.TypeHint.InlineData:
- inlineDataHints++;
- break;
- case CodeAnalysis.TypeHint.NoHint:
- noHints++;
- break;
- default:
- Debug.Assert(false);
- break;
+ //
+ // For multi-line items, the OffsetSpan of the first item covers the entire
+ // item (it's the same for all Line instances), so we only want to do this for
+ // the first entry.
+ if (line.SubLineIndex == 0) {
+ for (int offset = line.FileOffset; offset < line.FileOffset + line.OffsetSpan;
+ offset++) {
+ switch (mProject.TypeHints[offset]) {
+ case CodeAnalysis.TypeHint.Code:
+ codeHints++;
+ break;
+ case CodeAnalysis.TypeHint.Data:
+ dataHints++;
+ break;
+ case CodeAnalysis.TypeHint.InlineData:
+ inlineDataHints++;
+ break;
+ case CodeAnalysis.TypeHint.NoHint:
+ noHints++;
+ break;
+ default:
+ Debug.Assert(false);
+ break;
+ }
}
}
}
- //Debug.WriteLine("GatherEntityCounts (len=" + CodeListGen.Count + ") took " +
- // (DateTime.Now - startWhen).TotalMilliseconds + " ms");
+ //Debug.WriteLine("GatherEntityCounts (start=" + startIndex + " end=" + endIndex +
+ // " len=" + mMainWin.CodeDisplayList.Count +
+ // ") took " + (DateTime.Now - startWhen).TotalMilliseconds + " ms");
return new EntityCounts() {
mCodeLines = codeLines,
diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs
index 1d27d85..a1626f2 100644
--- a/SourceGen/PseudoOp.cs
+++ b/SourceGen/PseudoOp.cs
@@ -36,7 +36,7 @@ namespace SourceGen {
private const int MAX_OPERAND_LEN = 64;
///
- /// One piece of the operand.
+ /// One piece of the pseudo-instruction.
///
public struct PseudoOut {
///
@@ -58,6 +58,8 @@ namespace SourceGen {
}
}
+ #region PseudoOpNames
+
///
/// Pseudo-op name collection. Instances are immutable.
///
@@ -238,6 +240,7 @@ namespace SourceGen {
{ "StrDci", ".dstr" }
});
+ #endregion PseudoOpNames
///
/// Computes the number of lines of output required to hold the formatted output.
@@ -248,6 +251,7 @@ namespace SourceGen {
public static int ComputeRequiredLineCount(Formatter formatter, PseudoOpNames opNames,
FormatDescriptor dfd, byte[] data, int offset) {
if (dfd.IsString) {
+ Debug.Assert(false); // shouldn't be calling here anymore
List lines = FormatStringOp(formatter, opNames, dfd, data,
offset, out string popcode);
return lines.Count;
@@ -275,7 +279,7 @@ namespace SourceGen {
/// Generates a pseudo-op statement for the specified data operation.
///
/// For most operations, only one output line will be generated. For larger items,
- /// like long comments, the value may be split into multiple lines. The sub-index
+ /// like dense hex, the value may be split into multiple lines. The sub-index
/// indicates which line should be formatted.
///
/// Format definition.
@@ -305,6 +309,7 @@ namespace SourceGen {
PseudoOut po = new PseudoOut();
if (dfd.IsString) {
+ Debug.Assert(false); // shouldn't be calling here anymore
List lines = FormatStringOp(formatter, opNames, dfd, data,
offset, out string popcode);
po.Opcode = popcode;
@@ -372,8 +377,8 @@ namespace SourceGen {
/// File data.
/// Offset, within data, of start of string.
/// Pseudo-opcode string.
- /// Array of strings.
- private static List FormatStringOp(Formatter formatter, PseudoOpNames opNames,
+ /// Array of operand strings.
+ public static List FormatStringOp(Formatter formatter, PseudoOpNames opNames,
FormatDescriptor dfd, byte[] data, int offset, out string popcode) {
int hiddenLeadingBytes = 0;
diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj
index fc28150..de56815 100644
--- a/SourceGen/SourceGen.csproj
+++ b/SourceGen/SourceGen.csproj
@@ -76,6 +76,7 @@
+
diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs
index f8ab083..ab5c956 100644
--- a/SourceGen/WpfGui/MainWindow.xaml.cs
+++ b/SourceGen/WpfGui/MainWindow.xaml.cs
@@ -732,43 +732,60 @@ namespace SourceGen.WpfGui {
// 50K: 10 seconds, 20K: 1.6 sec, 10K: 0.6 sec, 5K: 0.2 sec
const int MAX_SEL_COUNT = 5000;
- // The caller will clear the DisplayListSelection before calling here, so we
- // need to clear the ListView selection to match, even if we're about to call
- // SelectAll. If we don't, the SelectAll() call won't generate the necessary
- // events, and our DisplayListSelection will get out of sync.
- codeListView.SelectedItems.Clear();
+ TaskTimer timer = new TaskTimer();
+ timer.StartTask("TOTAL");
- if (sel.IsAllSelected()) {
- Debug.WriteLine("SetSelection: re-selecting all items");
- codeListView.SelectAll();
- return;
+ try {
+ timer.StartTask("Clear");
+ // The caller will clear the DisplayListSelection before calling here, so we
+ // need to clear the ListView selection to match, even if we're about to call
+ // SelectAll. If we don't, the SelectAll() call won't generate the necessary
+ // events, and our DisplayListSelection will get out of sync.
+ codeListView.SelectedItems.Clear();
+ timer.EndTask("Clear");
+
+ if (sel.IsAllSelected()) {
+ Debug.WriteLine("SetSelection: re-selecting all items");
+ timer.StartTask("SelectAll");
+ codeListView.SelectAll();
+ timer.EndTask("SelectAll");
+ return;
+ }
+
+ if (sel.Count > MAX_SEL_COUNT) {
+ // Too much for WPF ListView -- only restore the first item.
+ Debug.WriteLine("SetSelection: not restoring (" + sel.Count + " items)");
+ codeListView.SelectedItems.Add(CodeDisplayList[sel.GetFirstSelectedIndex()]);
+ return;
+ }
+
+ Debug.WriteLine("SetSelection: selecting " + sel.Count + " of " +
+ CodeDisplayList.Count);
+
+ // Note: if you refresh the display list with F5, the selection will be lost. This
+ // appears to be a consequence of hitting a key -- changing from the built-in
+ // "Refresh" command to a locally defined "Re-analyze" command bound to F6 didn't
+ // change the behavior. Selecting "re-analyze" from the DEBUG menu doesn't lose
+ // the selection.
+
+ timer.StartTask("tmpArray " + sel.Count);
+ DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[sel.Count];
+ int ai = 0;
+ foreach (int listIndex in sel) {
+ tmpArray[ai++] = CodeDisplayList[listIndex];
+ }
+ timer.EndTask("tmpArray " + sel.Count);
+
+ // Use a reflection call to provide the full set. This is much faster than
+ // adding the items one at a time to SelectedItems. (For one thing, it only
+ // invokes the SelectionChanged method once.)
+ timer.StartTask("Invoke");
+ listViewSetSelectedItems.Invoke(codeListView, new object[] { tmpArray });
+ timer.EndTask("Invoke");
+ } finally {
+ timer.EndTask("TOTAL");
+ //timer.DumpTimes("CodeListView_SetSelection");
}
-
- if (sel.Count > MAX_SEL_COUNT) {
- // Too much for WPF ListView -- only restore the first item.
- Debug.WriteLine("SetSelection: not restoring (" + sel.Count + " items)");
- codeListView.SelectedItems.Add(CodeDisplayList[sel.GetFirstSelectedIndex()]);
- return;
- }
-
- Debug.WriteLine("SetSelection: selecting " + sel.Count + " of " +
- CodeDisplayList.Count);
-
- //DateTime startWhen = DateTime.Now;
-
- DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[sel.Count];
- int ai = 0;
- foreach (int listIndex in sel) {
- tmpArray[ai++] = CodeDisplayList[listIndex];
- }
-
- // Use a reflection call to provide the full set. This is much faster than
- // adding the items one at a time to SelectedItems. (For one thing, it only
- // invokes the SelectionChanged method once.)
- listViewSetSelectedItems.Invoke(codeListView, new object[] { tmpArray });
-
- //Debug.WriteLine("SetSelection on " + sel.Count + " items took " +
- // (DateTime.Now - startWhen).TotalMilliseconds + " ms");
}
public void CodeListView_DebugValidateSelectionCount() {