diff --git a/SourceGen/DailyTips.cs b/SourceGen/DailyTips.cs
index bd45b5c..72c4f67 100644
--- a/SourceGen/DailyTips.cs
+++ b/SourceGen/DailyTips.cs
@@ -106,11 +106,11 @@ namespace SourceGen {
public int DailyNumber {
get {
// We show a different tip every day by taking the day-of-year value and
- // modding it by the number of tips we have. Doesn't do the right thing
- // at the end of year transition, but everybody is off partying anyway.
+ // modding it by the number of tips we have.
if (mTips.Count > 0) {
- int doy = DateTime.Now.DayOfYear;
- return doy % mTips.Count;
+ DateTime now = DateTime.Now;
+ int dayIndex = now.Year * 365 + now.DayOfYear;
+ return dayIndex % mTips.Count;
} else {
return 0;
}
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index c8c723b..f5e6b37 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -4320,6 +4320,9 @@ namespace SourceGen {
//sb.Append("DEBUG: opAddr=" + attr.OperandAddress.ToString("x4") +
// " opOff=" + attr.OperandOffset.ToString("x4") + "\r\n");
+ if (attr.NoContinueScript) {
+ sb.AppendLine("\"No-continue\" flag set by script");
+ }
if (attr.HasAnalyzerTag) {
sb.Append("\u2022 Analyzer Tags: ");
for (int i = 0; i < line.OffsetSpan; i++) {
diff --git a/SourceGen/RuntimeData/Common/StdInline.cs b/SourceGen/RuntimeData/Common/StdInline.cs
index bc39fcb..b81e364 100644
--- a/SourceGen/RuntimeData/Common/StdInline.cs
+++ b/SourceGen/RuntimeData/Common/StdInline.cs
@@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
+
using PluginCommon;
namespace RuntimeData.Common {
@@ -37,9 +38,19 @@ namespace RuntimeData.Common {
/// ASCII functions work for standard and high ASCII, auto-detecting the encoding based on
/// the first character.
///
+ ///
+ /// As an optimization, we use a lookup table keyed by address, and another keyed by offset.
+ /// For a project that doesn't have overlapping address spaces this wouldn't be necessary,
+ /// and we could just map the address (JSR operand) to the inline data type. Since this
+ /// code is meant be a general-purpose, we need to use the offset, but that requires a lookup
+ /// in the address translation table, which we would prefer to avoid doing for every JSR in
+ /// the project. So we do a quick check on the address first, and only do the offset
+ /// translation if it looks like a possible match.
+ ///
public class StdInline : MarshalByRefObject, IPlugin, IPlugin_SymbolList, IPlugin_InlineJsr {
private IApplication mAppRef;
private byte[] mFileData;
+ private AddressTranslate mAddrTrans;
private class NameMap {
public string Prefix { get; private set; }
@@ -60,8 +71,11 @@ namespace RuntimeData.Common {
new NameMap("InWA_", InlineKind.InWA),
};
- // Map of addresses (not offsets) in project to inline data handled by code there.
- private Dictionary mInlineLabels = new Dictionary();
+ // Map of JSR offsets in project to inline data type expected to follow.
+ private Dictionary mInlineOffsets = new Dictionary();
+
+ // List of "interesting" addresses. Used as an optimization.
+ private Dictionary mInlineAddrs = new Dictionary();
// IPlugin
public string Identifier {
@@ -69,9 +83,10 @@ namespace RuntimeData.Common {
}
// IPlugin
- public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate unused) {
+ public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) {
mAppRef = appRef;
mFileData = fileData;
+ mAddrTrans = addrTrans;
mAppRef.DebugLog("StdInline(id=" + AppDomain.CurrentDomain.Id + "): prepare()");
}
@@ -80,31 +95,34 @@ namespace RuntimeData.Common {
public void Unprepare() {
mAppRef = null;
mFileData = null;
+ mAddrTrans = null;
}
// IPlugin_SymbolList
public void UpdateSymbolList(List plSyms) {
- mInlineLabels.Clear();
+ mInlineOffsets.Clear();
+ mInlineAddrs.Clear();
- // Find matching symbols. Save the symbol's value (its address) and the type.
- // We want an exact match on L1STR_NAME, and prefix matches on the other two.
+ // Find matching symbols.
foreach (PlSymbol sym in plSyms) {
- // We might want to ignore user labels in non-addressable regions, which all
- // show up with NON_ADDR as their address. In practice it doesn't matter.
+ if (sym.Value == AddressTranslate.NON_ADDR) {
+ // The non-addressable target won't be returned by the address-to-offset
+ // lookup, so this doesn't change the behavior. But there's no value in
+ // having NON_ADDR in the lookup table, so strip it out now.
+ //mAppRef.DebugLog("Ignoring non-addr label '" + sym.Label + "'");
+ continue;
+ }
foreach (NameMap map in sMap) {
if (sym.Label.StartsWith(map.Prefix)) {
- // Multiple offsets could have the same address. Map the first.
- if (!mInlineLabels.ContainsKey(sym.Value)) {
- mInlineLabels.Add(sym.Value, map.Kind);
- } else {
- mAppRef.DebugLog("Ignoring duplicate address " +
- sym.Value.ToString("x4"));
- }
+ // Offsets will be unique.
+ mInlineOffsets.Add(sym.Offset, map.Kind);
+ // Symbol values (addresses) may not be unique.
+ mInlineAddrs[sym.Value] = sym.Value;
break;
}
}
}
- mAppRef.DebugLog("Found matches for " + mInlineLabels.Count + " labels");
+ mAppRef.DebugLog("Found matches for " + mInlineOffsets.Count + " labels");
}
// IPlugin_SymbolList
@@ -124,12 +142,26 @@ namespace RuntimeData.Common {
public void CheckJsr(int offset, int operand, out bool noContinue) {
noContinue = false;
- InlineKind kind;
- if (!mInlineLabels.TryGetValue(operand, out kind)) {
+ // Do a quick test on the address.
+ int unused;
+ if (!mInlineAddrs.TryGetValue(operand, out unused)) {
// JSR destination address not recognized.
return;
}
+ // Address matched. Translate the target address to the actual offset. This is
+ // important when multiple offsets have the same address.
+ int targetOffset = mAddrTrans.AddressToOffset(offset, operand);
+ if (targetOffset < 0) {
+ mAppRef.DebugLog("Failed to map address $" + operand.ToString("x4") + " to offset");
+ return;
+ }
+ InlineKind kind;
+ if (!mInlineOffsets.TryGetValue(targetOffset, out kind)) {
+ // Actual call target doesn't have a matching label.
+ return;
+ }
+
offset += 3; // move past JSR
switch (kind) {
diff --git a/SourceGen/RuntimeData/Tips/daily-tips.json b/SourceGen/RuntimeData/Tips/daily-tips.json
index e025013..69510e6 100644
--- a/SourceGen/RuntimeData/Tips/daily-tips.json
+++ b/SourceGen/RuntimeData/Tips/daily-tips.json
@@ -34,7 +34,10 @@
"Image" : "note-sample.png"
},
{
- "Text" : "You're not limited to global labels. You can create non-unique local labels, like \"@LOOP\", and define multiple labels for zero-page addresses in local variable tables."
+ "Text" : "You're not limited to global labels. You can create non-unique local labels, like \"@LOOP\", and define multiple labels for zero-page addresses in Local Variable Tables."
+ },
+ {
+ "Text" : "You can copy and paste lines from the disassembly listing as text simply by selecting them and hitting Ctrl+C. This can be handy for bug reports and online forum postings. The set of columns copied can be chosen in the application settings."
},
{
"Text" : "2D bitmap images and 3D wireframe meshes can be converted to images that are displayed inline. This can make it much easier to figure out what a piece of code is drawing."
diff --git a/SourceGen/SGTestData/20270-std-inline b/SourceGen/SGTestData/20270-std-inline
index a151d1c..60f0f61 100644
Binary files a/SourceGen/SGTestData/20270-std-inline and b/SourceGen/SGTestData/20270-std-inline differ
diff --git a/SourceGen/SGTestData/20270-std-inline.dis65 b/SourceGen/SGTestData/20270-std-inline.dis65
index df1d859..f4faf28 100644
--- a/SourceGen/SGTestData/20270-std-inline.dis65
+++ b/SourceGen/SGTestData/20270-std-inline.dis65
@@ -1,8 +1,8 @@
### 6502bench SourceGen dis65 v1.0 ###
{
"_ContentVersion":5,
-"FileDataLength":210,
-"FileDataCrc32":-1608872177,
+"FileDataLength":251,
+"FileDataCrc32":-1864354559,
"ProjectProps":{
"CpuName":"6502",
"IncludeUndocumentedInstr":false,
@@ -28,10 +28,55 @@
"Addr":4096,
"Length":-1024,
"PreLabel":"",
+"IsRelative":false},
+
+{
+"Offset":184,
+"Addr":8192,
+"Length":-1024,
+"PreLabel":"",
+"IsRelative":false},
+
+{
+"Offset":192,
+"Addr":8192,
+"Length":-1024,
+"PreLabel":"",
+"IsRelative":false},
+
+{
+"Offset":200,
+"Addr":8192,
+"Length":-1024,
+"PreLabel":"",
+"IsRelative":false},
+
+{
+"Offset":209,
+"Addr":-1025,
+"Length":-1024,
+"PreLabel":"",
+"IsRelative":false},
+
+{
+"Offset":215,
+"Addr":61440,
+"Length":-1024,
+"PreLabel":"",
"IsRelative":false}],
"TypeHints":[{
"Low":0,
"High":0,
+"Hint":"Code"},
+
+{
+"Low":192,
+"High":192,
+"Hint":"Code"},
+
+{
+"Low":200,
+"High":200,
"Hint":"Code"}],
"StatusFlagOverrides":{
},
@@ -93,6 +138,34 @@
"Value":4105,
"Source":"User",
"Type":"GlobalAddr",
+"LabelAnno":"None"},
+
+"184":{
+"Label":"InW_test1",
+"Value":8192,
+"Source":"User",
+"Type":"GlobalAddr",
+"LabelAnno":"None"},
+
+"192":{
+"Label":"InW_test2",
+"Value":8192,
+"Source":"User",
+"Type":"GlobalAddr",
+"LabelAnno":"None"},
+
+"200":{
+"Label":"not_inline",
+"Value":8192,
+"Source":"User",
+"Type":"GlobalAddr",
+"LabelAnno":"None"},
+
+"209":{
+"Label":"InW_na_test",
+"Value":-1025,
+"Source":"User",
+"Type":"GlobalAddr",
"LabelAnno":"None"}},
"OperandFormats":{
diff --git a/SourceGen/SGTestData/Expected/20270-std-inline_64tass.S b/SourceGen/SGTestData/Expected/20270-std-inline_64tass.S
index 565b853..b7f86b4 100644
--- a/SourceGen/SGTestData/Expected/20270-std-inline_64tass.S
+++ b/SourceGen/SGTestData/Expected/20270-std-inline_64tass.S
@@ -50,16 +50,60 @@ L1040 nop
.byte $00
_L10AD nop
- jsr _L10B6
- jsr _L10C3
+ jsr InW_test1
+ .word $1100
+ nop
+ jmp LF000
+
+ .byte $80
+
+ .logical $2000
+InW_test1 nop
+ jsr InW_test1
+ .word $1200
+ rts
+
+ .byte $80
+ .here
+
+ .logical $2000
+InW_test2 nop
+ jsr InW_test2
+ .word $1300
+ rts
+
+ .byte $80
+ .here
+
+ .logical $2000
+not_inline nop
+ jsr not_inline
+ bit not_inline
+ rts
+
+ .byte $81
+ .here
+ .logical $0000
+InW_na_test .byte $ea
+ .byte $20
+ .byte $00
+ .byte $30
+ .byte $60
+ .byte $81
+ .here
+
+ .logical $f000
+LF000 jsr _LF008
+ jsr _LF015
nop
rts
-_L10B6 jsr InA1_test
+_LF008 jsr InA1_test
.byte $ff
.enc "sg_ascii"
.text "too long"
.byte $ea
-_L10C3 jsr InAZ_test
+_LF015 jsr InAZ_test
.text "does not end"
+ .here
diff --git a/SourceGen/SGTestData/Expected/20270-std-inline_acme.S b/SourceGen/SGTestData/Expected/20270-std-inline_acme.S
index 164742e..ded3ffd 100644
--- a/SourceGen/SGTestData/Expected/20270-std-inline_acme.S
+++ b/SourceGen/SGTestData/Expected/20270-std-inline_acme.S
@@ -42,15 +42,59 @@ L1040 nop
!byte $00
@L10AD nop
- jsr @L10B6
- jsr @L10C3
+ jsr InW_test1
+ !word $1100
+ nop
+ jmp LF000
+
+ !byte $80
+
+ !pseudopc $2000 {
+InW_test1 nop
+ jsr InW_test1
+ !word $1200
+ rts
+
+ !byte $80
+ }
+
+ !pseudopc $2000 {
+InW_test2 nop
+ jsr InW_test2
+ !word $1300
+ rts
+
+ !byte $80
+ }
+
+ !pseudopc $2000 {
+not_inline nop
+ jsr not_inline
+ bit not_inline
+ rts
+
+ !byte $81
+ }
+ !pseudopc $0000 {
+InW_na_test !byte $ea
+ !byte $20
+ !byte $00
+ !byte $30
+ !byte $60
+ !byte $81
+ }
+
+ !pseudopc $f000 {
+LF000 jsr @LF008
+ jsr @LF015
nop
rts
-@L10B6 jsr InA1_test
+@LF008 jsr InA1_test
!byte $ff
!text "too long"
!byte $ea
-@L10C3 jsr InAZ_test
+@LF015 jsr InAZ_test
!text "does not end"
+ }
diff --git a/SourceGen/SGTestData/Expected/20270-std-inline_cc65.S b/SourceGen/SGTestData/Expected/20270-std-inline_cc65.S
index d0859fb..e0ad475 100644
--- a/SourceGen/SGTestData/Expected/20270-std-inline_cc65.S
+++ b/SourceGen/SGTestData/Expected/20270-std-inline_cc65.S
@@ -42,15 +42,54 @@ L1040: nop
.byte $00
@L10AD: nop
- jsr @L10B6
- jsr @L10C3
+ jsr InW_test1
+ .word $1100
+ nop
+ jmp LF000
+
+ .byte $80
+
+ .org $2000
+InW_test1: nop
+ jsr InW_test1
+ .word $1200
+ rts
+
+ .byte $80
+
+ .org $2000
+InW_test2: nop
+ jsr InW_test2
+ .word $1300
+ rts
+
+ .byte $80
+
+ .org $2000
+not_inline: nop
+ jsr not_inline
+ bit not_inline
+ rts
+
+ .byte $81
+ .org $0000
+InW_na_test: .byte $ea
+ .byte $20
+ .byte $00
+ .byte $30
+ .byte $60
+ .byte $81
+
+ .org $f000
+LF000: jsr @LF008
+ jsr @LF015
nop
rts
-@L10B6: jsr InA1_test
+@LF008: jsr InA1_test
.byte $ff
.byte "too long"
.byte $ea
-@L10C3: jsr InAZ_test
+@LF015: jsr InAZ_test
.byte "does not end"
diff --git a/SourceGen/SGTestData/Expected/20270-std-inline_merlin32.S b/SourceGen/SGTestData/Expected/20270-std-inline_merlin32.S
index b8bd7b0..9de7742 100644
--- a/SourceGen/SGTestData/Expected/20270-std-inline_merlin32.S
+++ b/SourceGen/SGTestData/Expected/20270-std-inline_merlin32.S
@@ -41,15 +41,54 @@ L1040 nop
dfb $00
:L10AD nop
- jsr :L10B6
- jsr :L10C3
+ jsr InW_test1
+ dw $1100
+ nop
+ jmp LF000
+
+ dfb $80
+
+ org $2000
+InW_test1 nop
+ jsr InW_test1
+ dw $1200
+ rts
+
+ dfb $80
+
+ org $2000
+InW_test2 nop
+ jsr InW_test2
+ dw $1300
+ rts
+
+ dfb $80
+
+ org $2000
+not_inline nop
+ jsr not_inline
+ bit not_inline
+ rts
+
+ dfb $81
+ org $0000
+InW_na_test dfb $ea
+ dfb $20
+ dfb $00
+ dfb $30
+ dfb $60
+ dfb $81
+
+ org $f000
+LF000 jsr :LF008
+ jsr :LF015
nop
rts
-:L10B6 jsr InA1_test
+:LF008 jsr InA1_test
dfb $ff
asc 'too long'
dfb $ea
-:L10C3 jsr InAZ_test
+:LF015 jsr InAZ_test
asc 'does not end'
diff --git a/SourceGen/SGTestData/Source/20270-std-inline.S b/SourceGen/SGTestData/Source/20270-std-inline.S
index de44f5b..ea6f0d1 100644
--- a/SourceGen/SGTestData/Source/20270-std-inline.S
+++ b/SourceGen/SGTestData/Source/20270-std-inline.S
@@ -56,7 +56,62 @@ calls nop
brk
cont nop
+
+; Test having multiple address spaces with the same target address.
+; Two of the spaces have a matching symbol, one doesn't. If we
+; match strictly by address we'll get it wrong.
+ jsr f_W_2k1
+ !word $1100
+
+ nop
+ jmp end_stuff
+
+ !byte $80
+
+ !pseudopc $2000 { ;EDIT: add address space, set label InW_
+f_W_2k1 nop
+ jsr f_W_2k1
+ !word $1200
+ rts
+ }
+
+ !byte $80
+
+ !pseudopc $2000 { ;EDIT: add address space, set label InW_
+f_W_2k2 nop
+ jsr f_W_2k2
+ !word $1300
+ rts
+ }
+
+ !byte $80
+
+ !pseudopc $2000 { ;EDIT: add address space, no label
+notspec nop
+ jsr notspec
+ bit notspec
+ rts
+ }
+
+ !byte $81
+
+; Test having a label in a non-addressable area. The formatter should
+; ignore it, since such areas can't have code in them. Note we can't
+; actually call it, since that would require referencing a label in a
+; non-addressable region, so we're really just using this as a way to
+; exercise the setup code in the script.
+ !pseudopc $3000 { ;EDIT: add NA address space, set label InW_
+f_W_na nop
+ jsr f_W_na
+ rts
+ }
+
+ !byte $81
+
+
+ !pseudopc $f000 {
; end-of-file error cases
+end_stuff
jsr end_err1
jsr end_err2
@@ -70,3 +125,4 @@ end_err1
end_err2
jsr f_AZ
!text "does not end" ;must be last
+ }