diff --git a/SourceGen/AppSettings.cs b/SourceGen/AppSettings.cs index 2f34016..d444f81 100644 --- a/SourceGen/AppSettings.cs +++ b/SourceGen/AppSettings.cs @@ -77,6 +77,7 @@ namespace SourceGen { // Symbol-list window options. public const string SYMWIN_SHOW_USER = "symwin-show-user"; + public const string SYMWIN_SHOW_NON_UNIQUE = "symwin-show-non-unique"; public const string SYMWIN_SHOW_AUTO = "symwin-show-auto"; public const string SYMWIN_SHOW_PROJECT = "symwin-show-project"; public const string SYMWIN_SHOW_PLATFORM = "symwin-show-platform"; diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 4f6ad03..e2ae299 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -308,11 +308,12 @@ namespace SourceGen { // actually used is expected to do something reasonable by default. settings.SetBool(AppSettings.SYMWIN_SHOW_USER, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_NON_UNIQUE, false); settings.SetBool(AppSettings.SYMWIN_SHOW_PROJECT, true); settings.SetBool(AppSettings.SYMWIN_SHOW_PLATFORM, false); settings.SetBool(AppSettings.SYMWIN_SHOW_AUTO, false); - settings.SetBool(AppSettings.SYMWIN_SHOW_CONST, true); settings.SetBool(AppSettings.SYMWIN_SHOW_ADDR, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_CONST, true); settings.SetBool(AppSettings.SYMWIN_SORT_ASCENDING, true); settings.SetInt(AppSettings.SYMWIN_SORT_COL, (int)Symbol.SymbolSortField.Name); @@ -502,6 +503,8 @@ namespace SourceGen { // Configure the Symbols window. mMainWin.SymFilterUserLabels = settings.GetBool(AppSettings.SYMWIN_SHOW_USER, false); + mMainWin.SymFilterNonUniqueLabels = + settings.GetBool(AppSettings.SYMWIN_SHOW_NON_UNIQUE, false); mMainWin.SymFilterAutoLabels = settings.GetBool(AppSettings.SYMWIN_SHOW_AUTO, false); mMainWin.SymFilterProjectSymbols = diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index 3b79be3..2d339ce 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -279,7 +279,7 @@ namespace SourceGen { public SerSymbol() { } public SerSymbol(Symbol sym) { - Label = sym.Label; + Label = sym.LabelForSerialization; // use bare label here Value = sym.Value; Source = sym.SymbolSource.ToString(); Type = sym.SymbolType.ToString(); @@ -308,7 +308,7 @@ namespace SourceGen { public SerWeakSymbolRef() { } public SerWeakSymbolRef(WeakSymbolRef weakSym) { - Label = weakSym.Label; + Label = weakSym.Label; // retain non-unique tag in weak refs Part = weakSym.ValuePart.ToString(); } } @@ -642,7 +642,7 @@ namespace SourceGen { continue; } - if (!CreateSymbol(kvp.Value, report, out Symbol newSym)) { + if (!CreateSymbol(kvp.Value, intKey, report, out Symbol newSym)) { continue; } if (newSym.SymbolSource != Symbol.Source.User) { @@ -656,13 +656,15 @@ namespace SourceGen { // Check for duplicate labels. We only want to compare label strings, so we // can't test UserLabels.ContainsValue (which might be a linear search anyway). // Dump the labels into a sorted list. - if (labelDupCheck.ContainsKey(kvp.Value.Label)) { + // + // We want to use newSym.Label rather than kvp.Value.Label, because the latter + // won't have the non-unique local tag. + if (labelDupCheck.ContainsKey(newSym.Label)) { report.Add(FileLoadItem.Type.Warning, - string.Format(Res.Strings.ERR_DUPLICATE_LABEL_FMT, - kvp.Value.Label, intKey)); + string.Format(Res.Strings.ERR_DUPLICATE_LABEL_FMT, newSym.Label, intKey)); continue; } - labelDupCheck.Add(kvp.Value.Label, string.Empty); + labelDupCheck.Add(newSym.Label, string.Empty); proj.UserLabels[intKey] = newSym; } @@ -720,11 +722,13 @@ namespace SourceGen { /// is generated in the FileLoadReport. /// /// Deserialized data. + /// If the symbol is a user label, this is the file offset. + /// If not, pass -1. Used for non-unique locals. /// Error report object. /// Created symbol. /// True on success. - private static bool CreateSymbol(SerSymbol ssym, FileLoadReport report, - out Symbol outSym) { + private static bool CreateSymbol(SerSymbol ssym, int userLabelOffset, + FileLoadReport report, out Symbol outSym) { outSym = null; Symbol.Source source; Symbol.Type type; @@ -736,12 +740,27 @@ namespace SourceGen { labelAnno = (Symbol.LabelAnnotation)Enum.Parse( typeof(Symbol.LabelAnnotation), ssym.LabelAnno); } + if (type == Symbol.Type.NonUniqueLocalAddr && source != Symbol.Source.User) { + throw new ArgumentException("unexpected source for non-unique local"); + } } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_ST + ": " + ssym.Source + "/" + ssym.Type); return false; } - outSym = new Symbol(ssym.Label, ssym.Value, source, type, labelAnno); + + if (!Asm65.Label.ValidateLabel(ssym.Label)) { + report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_LABEL + + ": " + ssym.Label); + return false; + } + + if (type == Symbol.Type.NonUniqueLocalAddr) { + outSym = new Symbol(ssym.Label, ssym.Value, labelAnno, userLabelOffset); + } else { + outSym = new Symbol(ssym.Label, ssym.Value, source, type, labelAnno); + } + return true; } @@ -757,7 +776,7 @@ namespace SourceGen { FileLoadReport report, out DefSymbol outDefSym) { outDefSym = null; - if (!CreateSymbol(serDefSym, report, out Symbol sym)) { + if (!CreateSymbol(serDefSym, -1, report, out Symbol sym)) { return false; } if (!CreateFormatDescriptor(serDefSym.DataDescriptor, contentVersion, report, diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index 911ddf1..cb28111 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -50,6 +50,7 @@ limitations under the License. Bad local variable {0} Invalid local variable table at +{0:x6} Bad range + Malformed label in symbol Unknown Source or Type in symbol Bad symbol reference part Type hint not recognized diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs index eb3e5ea..c958c29 100644 --- a/SourceGen/Res/Strings.xaml.cs +++ b/SourceGen/Res/Strings.xaml.cs @@ -81,6 +81,8 @@ namespace SourceGen.Res { (string)Application.Current.FindResource("str_ErrBadLvTableFmt"); public static string ERR_BAD_RANGE = (string)Application.Current.FindResource("str_ErrBadRange"); + public static string ERR_BAD_SYMBOL_LABEL = + (string)Application.Current.FindResource("str_ErrBadSymbolLabel"); public static string ERR_BAD_SYMBOL_ST = (string)Application.Current.FindResource("str_ErrBadSymbolSt"); public static string ERR_BAD_SYMREF_PART = diff --git a/SourceGen/SGTestData/Source/2023-non-unique-labels.S b/SourceGen/SGTestData/Source/2023-non-unique-labels.S new file mode 100644 index 0000000..8525acc --- /dev/null +++ b/SourceGen/SGTestData/Source/2023-non-unique-labels.S @@ -0,0 +1,88 @@ +; Copyright 2019 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). +; +; Assembler: Merlin 32 + + org $1000 + +; Test conflict with auto-label. +start lda #$00 ;do not label +:L1000 lda #$01 ;EDIT: set label to :L1000 (dup of auto) + ldx start + ldy :L1000 + +; Test local/global having same name. + ldx #$02 +loop1 dex ;EDIT + bne loop1 + + ldx #$03 +:loop1 dex ;EDIT + bne :loop1 + +; Test nested loops, and ref to a non-unique local on the other side +; of a global. +global1 nop ;EDIT + ldx #$04 +:loop1 ldy #$05 ;EDIT: local, name "loop" + +:loop2 dey ;EDIT: local, name "loop" + bne :loop2 + dex + bne :loop1 + jmp btarg + +global2 nop ;EDIT + +btarg nop ;EDIT: local, name "loop" + +; Test hand-over-hand locals branching forward. +global3 nop ;EDIT + ldx #$06 + ldy #$07 + dex + beq :fwd1 + dey + beq :fwd2 +:fwd1 nop ;EDIT +:fwd2 nop ;EDIT + +; Test loop with a global in the middle. +global4 nop ;EDIT + ldx #$08 +gloop dex ;EDIT: local, name "loop" +global5 nop + bne gloop + + nop + +; Test symbolic references. + +global6 nop +:spin1 jsr :spin2 ;EDIT: local, name "spin1", operand ref to ":spin2" +:spin2 jsr :spin1 ;EDIT: local, name "spin2", operand ref to ":spin1" + nop +:spin3 lda :spin3 ;EDIT: local, name "spin1", operand ref to ":spin1" + +; Semi-related: test labels that are nothing but underscores. +global_ nop + ldx #$40 +__ dex + bne __ + beq ___ +___ ldx #$41 +:__ dex + bne :__ + + nop + +; Semi-related: test annotations (mostly to confirm that the suffix chars +; aren't appearing in the assembly output) +anno lda #$42 ;EDIT: add '?' +anno1 lda anno + + + rts + + dw anno1 ;EDIT: use table generator + diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index af15c1a..b36a786 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -118,6 +118,19 @@ namespace SourceGen { public string SourceTypeString { get; private set; } + /// + /// Label without the non-unique tag. + /// + public string LabelForSerialization { + get { + if (SymbolType != Type.NonUniqueLocalAddr) { + return Label; + } else { + return Label.Substring(0, Label.Length - NON_UNIQUE_LEN); + } + } + } + /// /// True if the symbol's type is an internal label (auto or user). Will be false /// for external addresses (including variables) and constants. @@ -234,26 +247,41 @@ namespace SourceGen { } /// - /// Converts a label to displayable form by stripping the uniquification tag (if any) - /// and appending the optional annotation. This is needed for display of WeakSymbolRefs. + /// Converts a label to displayable form by stripping the uniquification tag (if any), + /// inserting the non-unique label prefix if appropriate, and appending the optional + /// annotation character. /// + /// + /// There's generally two ways to display a label: + /// (1) When displaying labels on-screen, we get a label with the uniquification tag, + /// and we want to show the non-unique label prefix ('@' or ':') and annotation. + /// (2) When generating assembly source, we get a remapped label with no uniquification + /// tag, and we don't want to show the prefix or annotation. + /// For case #2, there's no reason to call here. (We're currently doing so because + /// remapping isn't happening yet, but that should change soon. When that happens, we + /// should be able to eliminate the showNonUnique arg.) + /// /// Base label string. Has the uniquification tag, but no /// annotation char or non-unique prefix. /// Annotation; may be None. + /// Set true if the returned label should show the + /// non-unique label prefix. + /// Format object that holds the non-unique label prefix + /// string. /// Formatted label. public static string ConvertLabelForDisplay(string label, LabelAnnotation anno, - bool isNonUnique, Asm65.Formatter formatter) { + bool showNonUnique, Asm65.Formatter formatter) { StringBuilder sb = new StringBuilder(label.Length + 2); - if (isNonUnique) { + if (showNonUnique) { sb.Append(formatter.NonUniqueLabelPrefix); } - // NOTE: could make this a length check + label[Length - NON_UNIQUE_LEN] - int nbrk = label.IndexOf(UNIQUE_TAG_CHAR); - if (nbrk >= 0) { - Debug.Assert(nbrk == label.Length - NON_UNIQUE_LEN); - sb.Append(label.Substring(0, nbrk)); + if (label.Length > NON_UNIQUE_LEN && + label[label.Length - NON_UNIQUE_LEN] == UNIQUE_TAG_CHAR) { + // showNonUnique may be false if generating assembly code (but by this + // point the unique tag should be remapped away) + sb.Append(label.Substring(0, label.Length - NON_UNIQUE_LEN)); } else { sb.Append(label); } @@ -273,7 +301,8 @@ namespace SourceGen { /// /// Label to examine. /// For address symbols, the prefix string for - /// non-unique labels. May be null if not validating a user label. + /// non-unique labels (e.g. '@' or ':'). May be null if not validating a user + /// label. /// True if the entire label is valid. /// True if the label has a valid length. /// True if the first character is valid. diff --git a/SourceGen/WpfGui/MainWindow.xaml b/SourceGen/WpfGui/MainWindow.xaml index 37eae72..50c8047 100644 --- a/SourceGen/WpfGui/MainWindow.xaml +++ b/SourceGen/WpfGui/MainWindow.xaml @@ -702,6 +702,8 @@ limitations under the License. + /// Symbols list DataGrid item. @@ -1659,6 +1670,7 @@ namespace SourceGen.WpfGui { return; } if ((SymFilterUserLabels != true && sli.Sym.SymbolSource == Symbol.Source.User) || + (SymFilterNonUniqueLabels != true && sli.Sym.IsNonUnique) || (SymFilterProjectSymbols != true && sli.Sym.SymbolSource == Symbol.Source.Project) || (SymFilterPlatformSymbols != true && sli.Sym.SymbolSource == Symbol.Source.Platform) || (SymFilterAutoLabels != true && sli.Sym.SymbolSource == Symbol.Source.Auto) ||