diff --git a/CommonUtil/AddressMap.cs b/CommonUtil/AddressMap.cs
index fb96db9..db7d85e 100644
--- a/CommonUtil/AddressMap.cs
+++ b/CommonUtil/AddressMap.cs
@@ -24,13 +24,13 @@ namespace CommonUtil {
/// multiple ORG directives.
///
/// It's possible to generate code that would overlap once relocated at run time,
- /// which means a given address could map to multiple offsets. For this reason
- /// it's useful to know the offset of the referring code when evaluating a
- /// reference, so that a "local" match can take priority.
+ /// which means a given address can map to multiple offsets (overlays, bank-switched
+ /// RAM, etc). For this reason it's useful to know the offset of the referring code
+ /// when evaluating a reference, so that "local" matches take priority.
///
///
/// This was part of the main SourceGen application, but I want to share it with
- /// script extensions.
+ /// the extension script mechanism.
///
public class AddressMap : IEnumerable {
public const int NO_ENTRY_ADDR = -1; // address value indicating no entry
@@ -44,31 +44,19 @@ namespace CommonUtil {
/// entry in the list. It's convenient to maintain it explicitly however, as
/// the list is read far more often than it is updated.
///
- /// Entries are mutable, but must only be altered by AddressMap. Don't retain
- /// instances of this across other activity.
+ /// Instances are immutable.
///
- ///
- /// TODO: make this immutable. That should allow us to eliminate the copy constructor,
- /// since we won't need to make copies of things.
- ///
[Serializable]
public class AddressMapEntry {
- public int Offset { get; set; }
- public int Addr { get; set; }
- public int Length { get; set; }
+ public int Offset { get; private set; }
+ public int Addr { get; private set; }
+ public int Length { get; private set; }
public AddressMapEntry(int offset, int addr, int len) {
Offset = offset;
Addr = addr;
Length = len;
}
-
- // Copy constructor.
- public AddressMapEntry(AddressMapEntry src) {
- Offset = src.Offset;
- Addr = src.Addr;
- Length = src.Length;
- }
}
///
@@ -99,7 +87,7 @@ namespace CommonUtil {
public AddressMap(List entries) {
mTotalLength = entries[entries.Count - 1].Offset + entries[entries.Count - 1].Length;
foreach (AddressMapEntry ent in entries) {
- mAddrList.Add(new AddressMapEntry(ent));
+ mAddrList.Add(ent);
}
DebugValidate();
}
@@ -111,7 +99,7 @@ namespace CommonUtil {
public List GetEntryList() {
List newList = new List(mAddrList.Count);
foreach (AddressMapEntry ent in mAddrList) {
- newList.Add(new AddressMapEntry(ent));
+ newList.Add(ent);
}
return newList;
}
@@ -186,8 +174,7 @@ namespace CommonUtil {
AddressMapEntry ad = mAddrList[i];
if (ad.Offset == offset) {
// update existing
- ad.Addr = addr;
- mAddrList[i] = ad;
+ mAddrList[i] = new AddressMapEntry(ad.Offset, addr, ad.Length);
return;
} else if (ad.Offset > offset) {
// The i'th entry is one past the interesting part.
@@ -199,8 +186,7 @@ namespace CommonUtil {
AddressMapEntry prev = mAddrList[i - 1];
int prevOldLen = prev.Length;
int prevNewLen = offset - prev.Offset;
- prev.Length = prevNewLen;
- mAddrList[i - 1] = prev;
+ mAddrList[i - 1] = new AddressMapEntry(prev.Offset, prev.Addr, prevNewLen);
mAddrList.Insert(i,
new AddressMapEntry(offset, addr, prevOldLen - prevNewLen));
@@ -223,8 +209,8 @@ namespace CommonUtil {
if (mAddrList[i].Offset == offset) {
// Add the length to the previous entry.
AddressMapEntry prev = mAddrList[i - 1];
- prev.Length += mAddrList[i].Length;
- mAddrList[i - 1] = prev;
+ mAddrList[i - 1] = new AddressMapEntry(prev.Offset, prev.Addr,
+ prev.Length + mAddrList[i].Length);
mAddrList.RemoveAt(i);
DebugValidate();
@@ -297,13 +283,14 @@ namespace CommonUtil {
}
///
- /// Checks to see if the specified range of offsets is in a contiguous range of
- /// addresses. Use this to see if something crosses an address-change boundary.
+ /// Checks to see if the specified range of offsets is in a single address range. Use
+ /// this to see if something crosses an address-change boundary. This does not
+ /// handle no-op address changes specially.
///
/// Start offset.
/// Length of region.
/// True if the data area is unbroken.
- public bool IsContiguous(int offset, int length) {
+ public bool IsSingleAddrRange(int offset, int length) {
Debug.Assert(offset >= 0 && offset < mTotalLength);
Debug.Assert(length > 0 && offset + length <= mTotalLength);
return (IndexForOffset(offset) == IndexForOffset(offset + length - 1));
diff --git a/SourceGen/CodeAnalysis.cs b/SourceGen/CodeAnalysis.cs
index c677321..112c523 100644
--- a/SourceGen/CodeAnalysis.cs
+++ b/SourceGen/CodeAnalysis.cs
@@ -1044,7 +1044,7 @@ namespace SourceGen {
" label='" + label + "'; file length is" + mFileData.Length);
}
- if (!mAddrMap.IsContiguous(offset, length)) {
+ if (!mAddrMap.IsSingleAddrRange(offset, length)) {
LogW(offset, "SIDF: format crosses address map boundary (len=" + length + ")");
return false;
}
diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs
index affec10..ebe0932 100644
--- a/SourceGen/DataAnalysis.cs
+++ b/SourceGen/DataAnalysis.cs
@@ -533,8 +533,8 @@ namespace SourceGen {
int startOffset = -1;
for (int offset = 0; offset < mAnattribs.Length; ) {
// We want to find a contiguous series of offsets which are not known
- // to hold code or data. We stop if we encounter a user-defined label
- // or format descriptor.
+ // to hold code or data. We stop if we encounter a user-defined label,
+ // format descriptor, or address override.
Anattrib attr = mAnattribs[offset];
if (attr.IsInstruction || attr.IsInlineData || attr.IsDataStart) {
@@ -572,19 +572,17 @@ namespace SourceGen {
}
offset++;
- // Check to see if the address has changed from the previous entry.
- // TODO(BUG): this test is insufficient -- they might have a .ORG that
- // doesn't change the address. It's currently harmless because the
- // .ORG is a no-op and gets swallowed up by the asm generator, but it
- // looks wrong and could break things.
+ // Check to see if we just crossed an address change.
if (offset < mAnattribs.Length &&
- mAnattribs[offset-1].Address + 1 != mAnattribs[offset].Address) {
- // Must be an ORG here. Scan previous region.
+ !mProject.AddrMap.IsSingleAddrRange(offset - 1, 2)) {
+ // Must be an ORG here. End region and scan.
AnalyzeRange(startOffset, offset - 1);
startOffset = -1;
}
}
}
+
+ // Do the last bit.
if (startOffset >= 0) {
AnalyzeRange(startOffset, mAnattribs.Length - 1);
}
diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs
index b4e4c82..527c1c2 100644
--- a/SourceGen/DisasmProject.cs
+++ b/SourceGen/DisasmProject.cs
@@ -969,7 +969,7 @@ namespace SourceGen {
continue;
}
- if (!AddrMap.IsContiguous(offset, dfd.Length)) {
+ if (!AddrMap.IsSingleAddrRange(offset, dfd.Length)) {
string msg = "descriptor straddles address change; len=" + dfd.Length;
genLog.LogE("+" + offset.ToString("x6") + ": " + msg);
Messages.Add(new MessageList.MessageEntry(
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index f3ea691..04c853a 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -2479,7 +2479,7 @@ namespace SourceGen {
// This must match what GroupedOffsetSetFromSelected() does.
if (!mProject.UserLabels.ContainsKey(nextOffset) &&
!mProject.HasCommentNoteOrVis(nextOffset) &&
- thisAttr.Address == nextAttr.Address - 1) {
+ mProject.AddrMap.IsSingleAddrRange(nextOffset - 1, 2)) {
// Good to go.
Debug.WriteLine("Grabbing second byte from +" + nextOffset.ToString("x6"));
trs.Add(nextOffset, rng.Type);
@@ -3362,6 +3362,9 @@ namespace SourceGen {
/// or a string. It should not be possible to select part of a formatted section,
/// unless the user has been playing weird games with type hints to get overlapping
/// format descriptors.
+ ///
+ /// The type values used in the TypedRangeSet may not be contiguous. They're only
+ /// there to create group separation from otherwise contiguous address ranges.
///
/// TypedRangeSet with all offsets.
private TypedRangeSet GroupedOffsetSetFromSelected() {
@@ -3394,12 +3397,18 @@ namespace SourceGen {
if (attr.Address != expectedAddr) {
// For a contiguous selection, this should only happen if there's a .ORG
// address change. For non-contiguous selection this is expected. In the
- // latter case, incrementing the group number is unnecessary but harmless.
- Debug.WriteLine("Address break: " + attr.Address + " vs. " + expectedAddr);
- //Debug.Assert(mProject.AddrMap.Get(offset) >= 0);
-
+ // latter case, incrementing the group number is unnecessary but harmless
+ // (the TypedRangeSet splits at the gap).
+ //Debug.WriteLine("Address break: $" + attr.Address.ToString("x4") + " vs. $"
+ // + expectedAddr.ToString("x4"));
expectedAddr = attr.Address;
groupNum++;
+ } else if (offset > 0 && !mProject.AddrMap.IsSingleAddrRange(offset - 1, 2)) {
+ // Was the previous byte in a different address range? This is only
+ // strictly necessary if the previous byte was in the selection set (which
+ // it won't be if the selection starts at the beginning of an address
+ // range), but bumping the group number is harmless if it wasn't.
+ groupNum++;
} else if (mProject.UserLabels.ContainsKey(offset)) {
//if (mProject.GetAnattrib(offset).Symbol != null) {
// We consider auto labels when splitting regions for the data analysis,
diff --git a/SourceGen/SGTestData/2004-numeric-types.dis65 b/SourceGen/SGTestData/2004-numeric-types.dis65
index eb025a3..6d9a8ac 100644
--- a/SourceGen/SGTestData/2004-numeric-types.dis65
+++ b/SourceGen/SGTestData/2004-numeric-types.dis65
@@ -1,89 +1,248 @@
### 6502bench SourceGen dis65 v1.0 ###
{
-"_ContentVersion":3,"FileDataLength":1200,"FileDataCrc32":1114187983,"ProjectProps":{
-"CpuName":"6502","IncludeUndocumentedInstr":false,"TwoByteBrk":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{
-"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true,"SmartPlpHandling":true},
-"PlatformSymbolFileIdentifiers":[],"ExtensionScriptFileIdentifiers":["PROJ:2004-numeric-types.cs"],"ProjectSyms":{
+"_ContentVersion":3,
+"FileDataLength":1200,
+"FileDataCrc32":1114187983,
+"ProjectProps":{
+"CpuName":"6502",
+"IncludeUndocumentedInstr":false,
+"TwoByteBrk":false,
+"EntryFlags":32702671,
+"AutoLabelStyle":"Simple",
+"AnalysisParams":{
+"AnalyzeUncategorizedData":true,
+"DefaultTextScanMode":"LowHighAscii",
+"MinCharsForString":4,
+"SeekNearbyTargets":true,
+"SmartPlpHandling":true},
+
+"PlatformSymbolFileIdentifiers":[],
+"ExtensionScriptFileIdentifiers":["PROJ:2004-numeric-types.cs"],
+"ProjectSyms":{
}},
+
"AddressMap":[{
-"Offset":0,"Addr":4096},
+"Offset":0,
+"Addr":4096},
+
{
-"Offset":1032,"Addr":5128},
+"Offset":1032,
+"Addr":5128},
+
{
-"Offset":1048,"Addr":5160}],"TypeHints":[{
-"Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{
+"Offset":1048,
+"Addr":5160}],
+"TypeHints":[{
+"Low":0,
+"High":0,
+"Hint":"Code"}],
+"StatusFlagOverrides":{
},
+
"Comments":{
-"588":"comment on bulk","882":"incorrect alignment","1128":"end-of-line comment"},
+"588":"comment on bulk",
+"882":"incorrect alignment",
+"1032":"note no-op .ORG",
+"1048":"meaningful .ORG",
+"1128":"end-of-line comment"},
+
"LongComments":{
"-2147483647":{
-"Text":"Project file was edited to get all big-endian data types, and to have an incorrect .junk alignment directive.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0},
+"Text":"Project file was edited to get all big-endian data types, and to have an incorrect .junk alignment directive.",
+"BoxMode":false,
+"MaxWidth":80,
+"BackgroundColor":0},
+
"1112":{
-"Text":"long comment\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}},
+"Text":"long comment\r\n",
+"BoxMode":false,
+"MaxWidth":80,
+"BackgroundColor":0}},
+
"Notes":{
"1144":{
-"Text":":ETON","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}},
+"Text":":ETON",
+"BoxMode":false,
+"MaxWidth":80,
+"BackgroundColor":0}},
+
"UserLabels":{
"588":{
-"Label":"LABEL","Value":4684,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"Label":"LABEL",
+"Value":4684,
+"Source":"User",
+"Type":"GlobalAddr",
+"LabelAnno":"None"},
+
"1064":{
-"Label":"UserLabel","Value":5176,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"}},
+"Label":"UserLabel",
+"Value":5176,
+"Source":"User",
+"Type":"GlobalAddr",
+"LabelAnno":"None"}},
+
"OperandFormats":{
"23":{
-"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
+"Length":1,
+"Format":"NumericLE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"24":{
-"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
+"Length":2,
+"Format":"NumericLE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"26":{
-"Length":3,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
+"Length":3,
+"Format":"NumericLE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"29":{
-"Length":4,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
+"Length":4,
+"Format":"NumericLE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"33":{
-"Length":1,"Format":"NumericBE","SubFormat":"Hex","SymbolRef":null},
+"Length":1,
+"Format":"NumericBE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"34":{
-"Length":2,"Format":"NumericBE","SubFormat":"Hex","SymbolRef":null},
+"Length":2,
+"Format":"NumericBE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"36":{
-"Length":3,"Format":"NumericBE","SubFormat":"Hex","SymbolRef":null},
+"Length":3,
+"Format":"NumericBE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"39":{
-"Length":4,"Format":"NumericBE","SubFormat":"Hex","SymbolRef":null},
+"Length":4,
+"Format":"NumericBE",
+"SubFormat":"Hex",
+"SymbolRef":null},
+
"43":{
-"Length":2,"Format":"Fill","SubFormat":"None","SymbolRef":null},
+"Length":2,
+"Format":"Fill",
+"SubFormat":"None",
+"SymbolRef":null},
+
"46":{
-"Length":3,"Format":"Fill","SubFormat":"None","SymbolRef":null},
+"Length":3,
+"Format":"Fill",
+"SubFormat":"None",
+"SymbolRef":null},
+
"50":{
-"Length":4,"Format":"Fill","SubFormat":"None","SymbolRef":null},
+"Length":4,
+"Format":"Fill",
+"SubFormat":"None",
+"SymbolRef":null},
+
"55":{
-"Length":5,"Format":"Fill","SubFormat":"None","SymbolRef":null},
+"Length":5,
+"Format":"Fill",
+"SubFormat":"None",
+"SymbolRef":null},
+
"61":{
-"Length":256,"Format":"Fill","SubFormat":"None","SymbolRef":null},
+"Length":256,
+"Format":"Fill",
+"SubFormat":"None",
+"SymbolRef":null},
+
"318":{
-"Length":257,"Format":"Fill","SubFormat":"None","SymbolRef":null},
+"Length":257,
+"Format":"Fill",
+"SubFormat":"None",
+"SymbolRef":null},
+
"575":{
-"Length":1,"Format":"Dense","SubFormat":"None","SymbolRef":null},
+"Length":1,
+"Format":"Dense",
+"SubFormat":"None",
+"SymbolRef":null},
+
"577":{
-"Length":10,"Format":"Dense","SubFormat":"None","SymbolRef":null},
+"Length":10,
+"Format":"Dense",
+"SubFormat":"None",
+"SymbolRef":null},
+
"588":{
-"Length":64,"Format":"Dense","SubFormat":"None","SymbolRef":null},
+"Length":64,
+"Format":"Dense",
+"SubFormat":"None",
+"SymbolRef":null},
+
"653":{
-"Length":115,"Format":"Junk","SubFormat":"Align256","SymbolRef":null},
+"Length":115,
+"Format":"Junk",
+"SubFormat":"Align256",
+"SymbolRef":null},
+
"769":{
-"Length":63,"Format":"Junk","SubFormat":"Align64","SymbolRef":null},
+"Length":63,
+"Format":"Junk",
+"SubFormat":"Align64",
+"SymbolRef":null},
+
"833":{
-"Length":31,"Format":"Junk","SubFormat":"Align32","SymbolRef":null},
+"Length":31,
+"Format":"Junk",
+"SubFormat":"Align32",
+"SymbolRef":null},
+
"864":{
-"Length":8,"Format":"Junk","SubFormat":"None","SymbolRef":null},
+"Length":8,
+"Format":"Junk",
+"SubFormat":"None",
+"SymbolRef":null},
+
"873":{
-"Length":8,"Format":"Junk","SubFormat":"None","SymbolRef":null},
+"Length":8,
+"Format":"Junk",
+"SubFormat":"None",
+"SymbolRef":null},
+
"882":{
-"Length":2,"Format":"Junk","SubFormat":"Align128","SymbolRef":null},
+"Length":2,
+"Format":"Junk",
+"SubFormat":"Align128",
+"SymbolRef":null},
+
"884":{
-"Length":140,"Format":"Junk","SubFormat":"Align256","SymbolRef":null},
+"Length":140,
+"Format":"Junk",
+"SubFormat":"Align256",
+"SymbolRef":null},
+
"1192":{
-"Length":1,"Format":"NumericLE","SubFormat":"Binary","SymbolRef":null}},
+"Length":1,
+"Format":"NumericLE",
+"SubFormat":"Binary",
+"SymbolRef":null}},
+
"LvTables":{
"1096":{
-"Variables":[],"ClearPrevious":true}},
+"Variables":[],
+"ClearPrevious":true}},
+
"Visualizations":[{
-"Tag":"vis000488","VisGenIdent":"dummy","VisGenParams":{
-}}],"VisualizationAnimations":[],"VisualizationSets":{
+"Tag":"vis000488",
+"VisGenIdent":"dummy",
+"VisGenParams":{
+}}],
+"VisualizationAnimations":[],
+"VisualizationSets":{
"1160":{
"Tags":["vis000488"]}}}
diff --git a/SourceGen/SGTestData/2024-ui-edge-cases b/SourceGen/SGTestData/2024-ui-edge-cases
new file mode 100644
index 0000000..bdcf36f
Binary files /dev/null and b/SourceGen/SGTestData/2024-ui-edge-cases differ
diff --git a/SourceGen/SGTestData/2024-ui-edge-cases.dis65 b/SourceGen/SGTestData/2024-ui-edge-cases.dis65
new file mode 100644
index 0000000..6f1e970
--- /dev/null
+++ b/SourceGen/SGTestData/2024-ui-edge-cases.dis65
@@ -0,0 +1,71 @@
+### 6502bench SourceGen dis65 v1.0 ###
+{
+"_ContentVersion":3,
+"FileDataLength":50,
+"FileDataCrc32":-1443914879,
+"ProjectProps":{
+"CpuName":"6502",
+"IncludeUndocumentedInstr":false,
+"TwoByteBrk":false,
+"EntryFlags":32702671,
+"AutoLabelStyle":"Simple",
+"AnalysisParams":{
+"AnalyzeUncategorizedData":true,
+"DefaultTextScanMode":"LowHighAscii",
+"MinCharsForString":4,
+"SeekNearbyTargets":true,
+"SmartPlpHandling":true},
+
+"PlatformSymbolFileIdentifiers":["RT:Apple/F8-ROM.sym65",
+"RT:Apple/Cxxx-IO.sym65",
+"RT:Apple/DOS33.sym65"],
+"ExtensionScriptFileIdentifiers":["RT:Apple/VisHiRes.cs"],
+"ProjectSyms":{
+}},
+
+"AddressMap":[{
+"Offset":0,
+"Addr":8192},
+
+{
+"Offset":10,
+"Addr":8202},
+
+{
+"Offset":16,
+"Addr":8448}],
+"TypeHints":[{
+"Low":0,
+"High":0,
+"Hint":"Code"}],
+"StatusFlagOverrides":{
+},
+
+"Comments":{
+"3":"string should be split by no-op addr change",
+"18":"edit this operand"},
+
+"LongComments":{
+},
+
+"Notes":{
+},
+
+"UserLabels":{
+"44":{
+"Label":"addr1",
+"Value":8476,
+"Source":"User",
+"Type":"GlobalAddr",
+"LabelAnno":"None"}},
+
+"OperandFormats":{
+},
+
+"LvTables":{
+},
+
+"Visualizations":[],
+"VisualizationAnimations":[],
+"VisualizationSets":{
+}}
diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S b/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S
index 9784107..8d3daea 100644
--- a/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S
+++ b/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S
@@ -63,10 +63,13 @@ LABEL .byte $00,$11,$22,$33,$44,$55,$66,$77,$88,$99,$aa,$bb,$cc,$dd,$ee,$ff
.byte $81
.fill 2,$dd ;incorrect alignment
.align 256,$00
- .fill 16,$82
+ .fill 8,$82
+ .logical $1408
+ .fill 8,$82 ;note no-op .ORG
.fill 8,$83
+ .here
.logical $1428
- .fill 8,$83
+ .fill 8,$83 ;meaningful .ORG
.fill 8,$84
UserLabel .fill 8,$84
.fill 8,$85
diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S b/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S
index ef6bf4d..c103745 100644
--- a/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S
+++ b/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S
@@ -58,10 +58,12 @@ LABEL hex 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
dfb $81
ds 2,$dd ;incorrect alignment
ds \
- ds 16,$82
+ ds 8,$82
+ org $1408
+ ds 8,$82 ;note no-op .ORG
ds 8,$83
org $1428
- ds 8,$83
+ ds 8,$83 ;meaningful .ORG
ds 8,$84
UserLabel ds 8,$84
ds 8,$85
diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S b/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S
index 0324208..69ed4b0 100644
--- a/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S
+++ b/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S
@@ -59,10 +59,13 @@ LABEL !hex 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
!byte $81
!fill 2,$dd ;incorrect alignment
!align 255,0,$00
- !fill 16,$82
+ !fill 8,$82
+ !pseudopc $1408 {
+ !fill 8,$82 ;note no-op .ORG
!fill 8,$83
+ } ;!pseudopc
!pseudopc $1428 {
- !fill 8,$83
+ !fill 8,$83 ;meaningful .ORG
!fill 8,$84
UserLabel !fill 8,$84
!fill 8,$85
diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S b/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S
index 5f21c6a..32edd7f 100644
--- a/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S
+++ b/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S
@@ -62,11 +62,14 @@ LABEL: .byte $00,$11,$22,$33,$44,$55,$66,$77,$88,$99,$aa,$bb,$cc,$dd,$ee,$ff
.byte $81
.res 2,$dd ;incorrect alignment
.res 140,$00
- .res 16,$82
+ .res 8,$82
+; .segment "SEG001"
+ .org $1408
+ .res 8,$82 ;note no-op .ORG
.res 8,$83
; .segment "SEG002"
.org $1428
- .res 8,$83
+ .res 8,$83 ;meaningful .ORG
.res 8,$84
UserLabel: .res 8,$84
.res 8,$85
diff --git a/SourceGen/SGTestData/Expected/2024-ui-edge-cases_64tass.S b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_64tass.S
new file mode 100644
index 0000000..38d3533
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_64tass.S
@@ -0,0 +1,25 @@
+ .cpu "6502"
+ .enc sg_ascii
+ .cdef $20,$7e,$20
+* = $2000
+ jmp L2100
+
+ .text "hello, " ;string should be split by no-op addr change
+ .logical $200a
+ .text "world"
+ .byte $80
+
+ .here
+ .logical $2100
+L2100 lda #$00
+ sta addr1-1 ;edit this operand
+ sta addr1
+ sta addr1+1
+ jmp L2121
+
+ .text "testing stuff."
+addr1 .text "!?---"
+
+L2121 rts
+
+ .here
diff --git a/SourceGen/SGTestData/Expected/2024-ui-edge-cases_Merlin32.S b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_Merlin32.S
new file mode 100644
index 0000000..532cea4
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_Merlin32.S
@@ -0,0 +1,20 @@
+ org $2000
+ jmp L2100
+
+ asc 'hello, ' ;string should be split by no-op addr change
+ org $200a
+ asc 'world'
+ dfb $80
+
+ org $2100
+L2100 lda #$00
+ sta addr1-1 ;edit this operand
+ sta addr1
+ sta addr1+1
+ jmp L2121
+
+ asc 'testing stuff.'
+addr1 asc '!?---'
+
+L2121 rts
+
diff --git a/SourceGen/SGTestData/Expected/2024-ui-edge-cases_acme.S b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_acme.S
new file mode 100644
index 0000000..b714551
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_acme.S
@@ -0,0 +1,23 @@
+ !cpu 6502
+* = $2000
+ jmp L2100
+
+ !text "hello, " ;string should be split by no-op addr change
+ !pseudopc $200a {
+ !text "world"
+ !byte $80
+
+ } ;!pseudopc
+ !pseudopc $2100 {
+L2100 lda #$00
+ sta addr1-1 ;edit this operand
+ sta addr1
+ sta addr1+1
+ jmp L2121
+
+ !text "testing stuff."
+addr1 !text "!?---"
+
+L2121 rts
+
+ } ;!pseudopc
diff --git a/SourceGen/SGTestData/Expected/2024-ui-edge-cases_cc65.S b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_cc65.S
new file mode 100644
index 0000000..aff9ea9
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_cc65.S
@@ -0,0 +1,24 @@
+ .setcpu "6502"
+; .segment "SEG000"
+ .org $2000
+ jmp L2100
+
+ .byte "hello, " ;string should be split by no-op addr change
+; .segment "SEG001"
+ .org $200a
+ .byte "world"
+ .byte $80
+
+; .segment "SEG002"
+ .org $2100
+L2100: lda #$00
+ sta addr1-1 ;edit this operand
+ sta addr1
+ sta addr1+1
+ jmp L2121
+
+ .byte "testing stuff."
+addr1: .byte "!?---"
+
+L2121: rts
+
diff --git a/SourceGen/SGTestData/Expected/2024-ui-edge-cases_cc65.cfg b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_cc65.cfg
new file mode 100644
index 0000000..114145f
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2024-ui-edge-cases_cc65.cfg
@@ -0,0 +1,15 @@
+# 6502bench SourceGen generated linker script for 2024-ui-edge-cases
+MEMORY {
+ MAIN: file=%O, start=%S, size=65536;
+# MEM000: file=%O, start=$2000, size=10;
+# MEM001: file=%O, start=$200a, size=6;
+# MEM002: file=%O, start=$2100, size=34;
+}
+SEGMENTS {
+ CODE: load=MAIN, type=rw;
+# SEG000: load=MEM000, type=rw;
+# SEG001: load=MEM001, type=rw;
+# SEG002: load=MEM002, type=rw;
+}
+FEATURES {}
+SYMBOLS {}
diff --git a/SourceGen/SGTestData/Source/2024-ui-edge-cases.S b/SourceGen/SGTestData/Source/2024-ui-edge-cases.S
new file mode 100644
index 0000000..4da7d23
--- /dev/null
+++ b/SourceGen/SGTestData/Source/2024-ui-edge-cases.S
@@ -0,0 +1,33 @@
+; Copyright 2020 faddenSoft. All Rights Reserved.
+; See the LICENSE.txt file for distribution terms (Apache 2.0).
+;
+; These tests are for exercising UI edge cases. They're not necessarily
+; meaningful for the generate/assemble regression test.
+;
+; Assembler: Merlin 32
+
+ ORG $2000
+
+ jmp skip
+
+ asc 'hello, '
+ ORG $200a ;EDIT: add this no-op ORG statement
+ asc 'world' ;(string finder should split the string)
+ dfb $80
+
+ org $2100 ;EDIT: add this
+skip
+ lda #$00
+ sta addr0 ;TEST: edit this operand ("addr1-1")
+ sta addr1
+ sta addr2
+ jmp done
+
+L1 asc 'testing stuff'
+addr0 asc '.'
+addr1 asc '!' ;EDIT: place label here
+addr2 asc '?'
+ asc '---'
+
+
+done rts