From 4d079c8d147ce73d5f3685ab2c2b0e59db72dacc Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 8 Nov 2019 20:44:45 -0800 Subject: [PATCH] Label rework, part 1 This adds the concept of label annotations. The primary driver of the feature is the desire to note that sometimes you know what a thing is, but sometimes you're just taking an educated guess. Instead of writing "high_score_maybe", you can now write "high_score?", which is more compact and consistent. The annotations are stripped off when generating source code, making them similar to Notes. I also created a "Generated" annotation for the labels that are synthesized by the address table formatter, but don't modify the label for them, because there's not much need to remind the user that "T1234" was generated by algorithm. This also lays some of the groundwork for non-unique labels. --- Asm65/Formatter.cs | 3 +- Asm65/Label.cs | 29 ++- SourceGen/AsmGen/AsmAcme.cs | 6 +- SourceGen/AsmGen/AsmCc65.cs | 6 +- SourceGen/AsmGen/AsmMerlin32.cs | 6 +- SourceGen/AsmGen/AsmTass64.cs | 6 +- SourceGen/AsmGen/GenCommon.cs | 16 +- SourceGen/AsmGen/LabelLocalizer.cs | 10 +- SourceGen/AutoLabel.cs | 2 +- SourceGen/DefSymbol.cs | 26 +- SourceGen/DisasmProject.cs | 12 +- SourceGen/LineListGen.cs | 10 +- SourceGen/LocalVariableLookup.cs | 8 + SourceGen/MainController.cs | 24 +- SourceGen/ProjectFile.cs | 34 +-- SourceGen/PseudoOp.cs | 31 ++- SourceGen/RuntimeData/Help/index.html | 3 +- SourceGen/RuntimeData/Help/intro.html | 93 ++++--- SourceGen/RuntimeData/Help/tutorials.html | 13 +- SourceGen/Symbol.cs | 234 +++++++++++++----- SourceGen/WpfGui/EditDefSymbol.xaml.cs | 18 +- .../WpfGui/EditInstructionOperand.xaml.cs | 6 +- SourceGen/WpfGui/EditLabel.xaml.cs | 42 +++- .../WpfGui/EditLocalVariableTable.xaml.cs | 29 +-- .../WpfGui/EditProjectProperties.xaml.cs | 38 ++- SourceGen/WpfGui/FormatAddressTable.xaml.cs | 2 +- 26 files changed, 469 insertions(+), 238 deletions(-) diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index e96e702..ac46ec7 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -613,7 +613,8 @@ namespace Asm65 { } /// - /// Formats a local variable label, prepending a prefix if needed. + /// Formats a local variable label, prepending an identifying prefix if one has been + /// specified. /// public string FormatVariableLabel(string label) { if (!string.IsNullOrEmpty(mFormatConfig.mLocalVariableLablePrefix)) { diff --git a/Asm65/Label.cs b/Asm65/Label.cs index 82b7602..0e7abb0 100644 --- a/Asm65/Label.cs +++ b/Asm65/Label.cs @@ -59,13 +59,40 @@ namespace Asm65 { /// Label to validate. /// True if the label is correctly formed. public static bool ValidateLabel(string label) { - if (label.Length > MAX_LABEL_LEN) { + if (label == null || label.Length > MAX_LABEL_LEN) { return false; } MatchCollection matches = sValidLabelCharRegex.Matches(label); return matches.Count == 1; } + /// + /// Performs a detailed validation of a symbol label, breaking out different failure + /// causes for the benefit of code that reports errors to the user. + /// + /// Label to examine. + /// True if the label has a valid length. + /// True if the first character is valid. + /// True if the label is valid. + public static bool ValidateLabelDetail(string label, out bool isLenValid, + out bool isFirstCharValid) { + bool isValid = ValidateLabel(label); + if (isValid) { + isLenValid = isFirstCharValid = true; + return true; + } + + // Something is wrong. Check length. + isLenValid = (label.Length >= 2 && label.Length <= MAX_LABEL_LEN); + + // Check first char for alphanumeric or underscore. + isFirstCharValid = label.Length > 0 && + ((label[0] >= 'A' && label[0] <= 'Z') || (label[0] >= 'a' && label[0] <= 'z') || + label[0] == '_'); + + return isValid; + } + /// /// Returns "normal form" of label. This matches LABEL_COMPARER behavior. /// diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index 070158b..3f860a5 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -387,7 +387,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -398,7 +398,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); } break; case FormatDescriptor.Type.Fill: @@ -515,7 +515,7 @@ namespace SourceGen.AsmGen { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); OutputEquDirective(SourceFormatter.FormatVariableLabel(defSym.Label), valueStr, defSym.Comment); } diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 7ea4e22..35cdfce 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -421,7 +421,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -432,7 +432,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); } break; case FormatDescriptor.Type.Fill: @@ -547,7 +547,7 @@ namespace SourceGen.AsmGen { // Use an operand length of 1 so values are shown as concisely as possible. string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 1ce3856..6faae0e 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -248,7 +248,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -259,7 +259,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); } break; case FormatDescriptor.Type.Fill: @@ -439,7 +439,7 @@ namespace SourceGen.AsmGen { foreach (DefSymbol defSym in newDefs) { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 41cd198..78bf284 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -453,7 +453,7 @@ namespace SourceGen.AsmGen { UpdateCharacterEncoding(dfd); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -465,7 +465,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); } break; case FormatDescriptor.Type.Fill: @@ -582,7 +582,7 @@ namespace SourceGen.AsmGen { foreach (DefSymbol defSym in newDefs) { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index 740d865..48c3db3 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -157,7 +157,7 @@ namespace SourceGen.AsmGen { // Use an operand length of 1 so values are shown as concisely as possible. string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); gen.OutputEquDirective(defSym.Label, valueStr, defSym.Comment); prevConst = defSym.IsConstant; @@ -204,7 +204,7 @@ namespace SourceGen.AsmGen { string formattedOperand = null; int operandLen = instrLen - 1; - PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None; + PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.StripAnnotation; bool isPcRelBankWrap = false; // Tweak branch instructions. We want to show the absolute address rather @@ -213,16 +213,16 @@ namespace SourceGen.AsmGen { if (op.AddrMode == OpDef.AddressMode.PCRel) { Debug.Assert(attr.OperandAddress >= 0); operandLen = 2; - opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel; + opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || op.AddrMode == OpDef.AddressMode.StackPCRelLong) { - opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel; + 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; + opFlags |= PseudoOp.FormatNumericOpFlags.HasHashPrefix; } - if (opFlags == PseudoOp.FormatNumericOpFlags.IsPcRel) { + if ((opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) != 0) { int branchDist = attr.Address - attr.OperandAddress; isPcRelBankWrap = branchDist > 32767 || branchDist < -32768; } @@ -245,10 +245,10 @@ namespace SourceGen.AsmGen { // Special handling for the double-operand block move. string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand >> 8, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand & 0xff, 1, - PseudoOp.FormatNumericOpFlags.None); + PseudoOp.FormatNumericOpFlags.StripAnnotation); if (gen.Quirks.BlockMoveArgsReversed) { string tmp = opstr1; opstr1 = opstr2; diff --git a/SourceGen/AsmGen/LabelLocalizer.cs b/SourceGen/AsmGen/LabelLocalizer.cs index d426411..c1a598d 100644 --- a/SourceGen/AsmGen/LabelLocalizer.cs +++ b/SourceGen/AsmGen/LabelLocalizer.cs @@ -81,12 +81,13 @@ namespace SourceGen.AsmGen { /// /// A pairing of an offset with a label string. (Essentially mAnattribs[n].Symbol /// with all the fluff trimmed away.) - /// + /// + /// /// The label string isn't actually all that useful, since we can pull it back out /// of anattrib, but it makes life a little easier during debugging. These get /// put into a List, so switching to a plain int offset doesn't necessarily help us /// much because the ints get boxed. - /// + /// private class OffsetLabel { public int Offset { get; private set; } public string Label { get; private set; } @@ -345,7 +346,8 @@ namespace SourceGen.AsmGen { LabelMap = new Dictionary(); } - // Throw out the original local label generation. + // Throw out the original local label generation. We're going to redo the + // label map generation step. LabelMap.Clear(); // Use this to test for uniqueness. We add all labels here as we go, not just the @@ -436,7 +438,7 @@ namespace SourceGen.AsmGen { // remapped, we add the remapped entry. // (All tested assemblers that failed on opcode names only did so for names // in their non-localized form. While "LSR" failed, "@LSR", "_LSR", ".LSR", etc. - // were accepted. So if it was remapped by the localizer, we don't need to + // were accepted. So if it was remapped by the localizer, we don't need to // worry about it.) SortedList allLabels = new SortedList(); for (int i = 0; i < mProject.FileDataLength; i++) { diff --git a/SourceGen/AutoLabel.cs b/SourceGen/AutoLabel.cs index 821037f..a6ada6e 100644 --- a/SourceGen/AutoLabel.cs +++ b/SourceGen/AutoLabel.cs @@ -70,7 +70,7 @@ namespace SourceGen { } } Symbol sym = new Symbol(label, addr, Symbol.Source.Auto, - Symbol.Type.LocalOrGlobalAddr); + Symbol.Type.LocalOrGlobalAddr, Symbol.LabelAnnotation.None); return sym; } diff --git a/SourceGen/DefSymbol.cs b/SourceGen/DefSymbol.cs index ccb9233..31a427d 100644 --- a/SourceGen/DefSymbol.cs +++ b/SourceGen/DefSymbol.cs @@ -170,8 +170,9 @@ namespace SourceGen { /// /// Internal base-object (Symbol) constructor, called by other constructors. /// - private DefSymbol(string label, int value, Source source, Type type) - : base(label, value, source, type) { + private DefSymbol(string label, int value, Source source, Type type, + LabelAnnotation labelAnno) + : base(label, value, source, type, labelAnno) { Debug.Assert(source == Source.Platform || source == Source.Project || source == Source.Variable); Debug.Assert(type == Type.ExternalAddr || type == Type.Constant); @@ -191,7 +192,7 @@ namespace SourceGen { /// user wants the value to be displayed. public DefSymbol(string label, int value, Source source, Type type, FormatDescriptor.SubType formatSubType) - : this(label, value, source, type, formatSubType, -1, false, + : this(label, value, source, type, LabelAnnotation.None, formatSubType, -1, false, string.Empty, DirectionFlags.ReadWrite, null, string.Empty) { } /// @@ -211,9 +212,10 @@ namespace SourceGen { /// Symbol tag, used for grouping platform symbols. /// false, the value of the "width" argument is ignored. public DefSymbol(string label, int value, Source source, Type type, - FormatDescriptor.SubType formatSubType, int width, bool widthSpecified, - string comment, DirectionFlags direction, MultiAddressMask multiMask, string tag) - : this(label, value, source, type) { + LabelAnnotation labelAnno, FormatDescriptor.SubType formatSubType, + int width, bool widthSpecified, string comment, + DirectionFlags direction, MultiAddressMask multiMask, string tag) + : this(label, value, source, type, labelAnno) { Debug.Assert(comment != null); Debug.Assert(tag != null); @@ -254,8 +256,8 @@ namespace SourceGen { FormatDescriptor.SubType formatSubType, int width, bool widthSpecified, string comment, DirectionFlags direction, MultiAddressMask multiMask, string tag, int loadOrdinal, string fileIdent) - : this(label, value, source, type, formatSubType, width, widthSpecified, - comment, direction, multiMask, tag) { + : this(label, value, source, type, LabelAnnotation.None, formatSubType, + width, widthSpecified, comment, direction, multiMask, tag) { LoadOrdinal = loadOrdinal; FileIdentifier = fileIdent; } @@ -281,7 +283,8 @@ namespace SourceGen { } Debug.Assert(dfd.FormatType == FormatDescriptor.Type.NumericLE); return new DefSymbol(sym.Label, sym.Value, sym.SymbolSource, sym.SymbolType, - dfd.FormatSubType, width, widthSpecified, comment, direction, multiMask, string.Empty); + sym.LabelAnno, dfd.FormatSubType, width, widthSpecified, + comment, direction, multiMask, string.Empty); } /// @@ -296,8 +299,9 @@ namespace SourceGen { /// Label to use. public DefSymbol(DefSymbol defSym, string label) : this(label, defSym.Value, defSym.SymbolSource, defSym.SymbolType, - defSym.DataDescriptor.FormatSubType, defSym.DataDescriptor.Length, - defSym.HasWidth, defSym.Comment, defSym.Direction, defSym.MultiMask, defSym.Tag) + defSym.LabelAnno, defSym.DataDescriptor.FormatSubType, + defSym.DataDescriptor.Length, defSym.HasWidth, defSym.Comment, + defSym.Direction, defSym.MultiMask, defSym.Tag) { } /// diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 2effea4..08f73cc 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -1100,10 +1100,10 @@ namespace SourceGen { /// private void UpdateAndMergeUserLabels() { // We store symbols as label+value, but for a user label the actual value is - // the address of the offset the label is associated with. It's convenient - // to store labels as Symbols because we also want the Type value, and it avoids - // having to create Symbol objects on the fly. If the value in the UserLabel - // is wrong, we fix it here. + // the address of the offset the label is associated with, which can change if + // the user updates the address map. It's convenient to store labels as Symbols + // because we also want the Type value, and it avoids having to create Symbol + // objects on the fly. If the value in the user label is wrong, we fix it here. Dictionary changes = new Dictionary(); @@ -1113,8 +1113,8 @@ namespace SourceGen { int expectedAddr = AddrMap.OffsetToAddress(offset); if (sym.Value != expectedAddr) { Symbol newSym = new Symbol(sym.Label, expectedAddr, sym.SymbolSource, - sym.SymbolType); - Debug.WriteLine("Replacing label sym: " + sym + " --> " + newSym); + sym.SymbolType, sym.LabelAnno); + Debug.WriteLine("Updating label value: " + sym + " --> " + newSym); changes[offset] = newSym; sym = newSym; } diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index bfd2f4a..666b007 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -871,7 +871,7 @@ namespace SourceGen { PseudoOp.FormatNumericOpFlags.None); valueStr = PseudoOp.AnnotateEquDirective(formatter, valueStr, defSym); string comment = formatter.FormatEolComment(defSym.Comment); - FormattedParts parts = FormattedParts.CreateEquDirective(defSym.Label, + FormattedParts parts = FormattedParts.CreateEquDirective(defSym.AnnotatedLabel, formatter.FormatPseudoOp(opNames.EquDirective), valueStr, comment); line.Parts = parts; @@ -1183,7 +1183,7 @@ namespace SourceGen { string labelStr = string.Empty; if (attr.Symbol != null) { - labelStr = attr.Symbol.Label; + labelStr = attr.Symbol.AnnotatedLabel; } OpDef op = mProject.CpuDef.GetOpDef(data[offset]); @@ -1316,7 +1316,7 @@ namespace SourceGen { addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16); if (attr.Symbol != null) { - labelStr = attr.Symbol.Label; + labelStr = attr.Symbol.AnnotatedLabel; } bytesStr = mFormatter.FormatBytes(data, offset, attr.Length); @@ -1394,7 +1394,7 @@ namespace SourceGen { addrStr = PseudoOp.AnnotateEquDirective(mFormatter, addrStr, defSym); string comment = mFormatter.FormatEolComment(defSym.Comment); return FormattedParts.CreateEquDirective( - mFormatter.FormatVariableLabel(defSym.Label), + mFormatter.FormatVariableLabel(defSym.AnnotatedLabel), mFormatter.FormatPseudoOp(mPseudoOpNames.VarDirective), addrStr, comment); } @@ -1436,7 +1436,7 @@ namespace SourceGen { addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16); if (attr.Symbol != null) { - labelStr = attr.Symbol.Label; + labelStr = attr.Symbol.AnnotatedLabel; } else { labelStr = string.Empty; } diff --git a/SourceGen/LocalVariableLookup.cs b/SourceGen/LocalVariableLookup.cs index a6facbb..c070696 100644 --- a/SourceGen/LocalVariableLookup.cs +++ b/SourceGen/LocalVariableLookup.cs @@ -22,6 +22,14 @@ namespace SourceGen { /// Given a list of LocalVariableTables, this determines the mapping of values to symbols /// at a specific offset. /// + /// + /// We guarantee that the label will be unique within its scope. This happens at two + /// different levels: + /// (1) If the local variable label is present in the main symbol table, we use the + /// "de-duplication" table to remap it. We try not to let this happen, but it can. + /// (2) If the assembler doesn't define a way to re-use variable names, we make them + /// globally unique. [currently unused] + /// public class LocalVariableLookup { /// /// List of tables. The table's file offset is used as the key. diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 96df507..bb577dd 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -3233,8 +3233,17 @@ namespace SourceGen { if (expectedAddr == -1) { expectedAddr = attr.Address; } - // Check for user labels. - if (mProject.UserLabels.ContainsKey(offset)) { + // Check for things that start a new group. + 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); + + expectedAddr = attr.Address; + groupNum++; + } else if (mProject.UserLabels.ContainsKey(offset)) { //if (mProject.GetAnattrib(offset).Symbol != null) { // We consider auto labels when splitting regions for the data analysis, // but I don't think we want to take them into account here. The specific @@ -3247,15 +3256,6 @@ namespace SourceGen { } else if (mProject.HasCommentOrNote(offset)) { // Don't carry across a long comment or note. groupNum++; - } else 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); - - expectedAddr = attr.Address; - groupNum++; } // Mark every byte of an instruction or multi-byte data item -- @@ -3475,7 +3475,7 @@ namespace SourceGen { } MainWindow.SymbolsListItem sli = new MainWindow.SymbolsListItem(sym, - sourceTypeStr, valueStr, sym.Label); + sourceTypeStr, valueStr, sym.AnnotatedLabel); mMainWin.SymbolsList.Add(sli); } } diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index daab0c2..3b79be3 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -52,7 +52,7 @@ namespace SourceGen { // ignore stuff that's in one side but not the other. However, if we're opening a // newer file in an older program, it's worth letting the user know that some stuff // may get lost as soon as they save the file. - public const int CONTENT_VERSION = 2; + public const int CONTENT_VERSION = 3; private static readonly bool ADD_CRLF = true; @@ -275,6 +275,7 @@ namespace SourceGen { public int Value { get; set; } public string Source { get; set; } public string Type { get; set; } + public string LabelAnno { get; set; } public SerSymbol() { } public SerSymbol(Symbol sym) { @@ -282,6 +283,7 @@ namespace SourceGen { Value = sym.Value; Source = sym.SymbolSource.ToString(); Type = sym.SymbolType.ToString(); + LabelAnno = sym.LabelAnno.ToString(); } } public class SerFormatDescriptor { @@ -640,17 +642,12 @@ namespace SourceGen { continue; } - Symbol.Source source; - Symbol.Type type; - try { - source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), kvp.Value.Source); - type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), kvp.Value.Type); - if (source != Symbol.Source.User) { - // User labels are always source=user. I don't think it really matters, - // but best to keep junk out. - throw new Exception("wrong source for user label"); - } - } catch (ArgumentException) { + if (!CreateSymbol(kvp.Value, report, out Symbol newSym)) { + continue; + } + if (newSym.SymbolSource != Symbol.Source.User) { + // User labels are always source=user. I don't think it really matters, + // but best to keep junk out. report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_ST + ": " + kvp.Value.Source + "/" + kvp.Value.Type); continue; @@ -667,8 +664,7 @@ namespace SourceGen { } labelDupCheck.Add(kvp.Value.Label, string.Empty); - proj.UserLabels[intKey] = new Symbol(kvp.Value.Label, kvp.Value.Value, - source, type); + proj.UserLabels[intKey] = newSym; } // Deserialize operand format descriptors. @@ -720,7 +716,8 @@ namespace SourceGen { } /// - /// Creates a Symbol from a SerSymbol. + /// Creates a Symbol from a SerSymbol. If it fails to parse correctly, an entry + /// is generated in the FileLoadReport. /// /// Deserialized data. /// Error report object. @@ -731,15 +728,20 @@ namespace SourceGen { outSym = null; Symbol.Source source; Symbol.Type type; + Symbol.LabelAnnotation labelAnno = Symbol.LabelAnnotation.None; try { source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), ssym.Source); type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), ssym.Type); + if (!string.IsNullOrEmpty(ssym.LabelAnno)) { + labelAnno = (Symbol.LabelAnnotation)Enum.Parse( + typeof(Symbol.LabelAnnotation), ssym.LabelAnno); + } } 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/*, ssym.IsExport*/); + outSym = new Symbol(ssym.Label, ssym.Value, source, type, labelAnno); return true; } diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 8ee15f9..eb4564e 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -547,10 +547,12 @@ namespace SourceGen { /// /// Special formatting flags for the FormatNumericOperand() method. /// + [Flags] public enum FormatNumericOpFlags { - None = 0, - IsPcRel, // opcode is PC relative, e.g. branch or PER - HasHashPrefix, // operand has a leading '#', avoiding ambiguity in some cases + None = 0, + IsPcRel = 1, // opcode is PC relative, e.g. branch or PER + HasHashPrefix = 1 << 1, // operand has a leading '#', avoiding ambiguity sometimes + StripAnnotation = 1 << 2, // don't show annotation character } /// @@ -595,7 +597,8 @@ namespace SourceGen { } /// - /// Format a numeric operand value according to the specified sub-format. + /// Format a numeric operand value according to the specified sub-format. This + /// version takes additional arguments to support local variables. /// /// Text formatter. /// Full table of project symbols. @@ -638,6 +641,8 @@ namespace SourceGen { Debug.Assert(operandLen == 1); // only doing 8-bit stuff DefSymbol defSym = lvLookup.GetSymbol(offset, dfd.SymbolRef); if (defSym != null) { + // For local variables we're doing trivial add/subtract and don't + // wrap, so the "common" format works for everybody. StringBuilder sb = new StringBuilder(); FormatNumericSymbolCommon(formatter, defSym, null, dfd, operandValue, operandLen, flags, sb); @@ -701,6 +706,10 @@ namespace SourceGen { if (sym.IsVariable) { symLabel = formatter.FormatVariableLabel(symLabel); } + // TODO(xyzzy): non-unique prefix + if ((flags & FormatNumericOpFlags.StripAnnotation) == 0) { + symLabel = Symbol.AppendAnnotation(symLabel, sym.LabelAnno); + } if (operandLen == 1) { // Use the byte-selection operator to get the right piece. In 64tass the @@ -756,7 +765,7 @@ namespace SourceGen { rightShift = 0; } - if (flags == FormatNumericOpFlags.IsPcRel) { + if ((flags & FormatNumericOpFlags.IsPcRel) != 0) { // PC-relative operands are funny, because an 8- or 16-bit value is always // expanded to 24 bits. We output a 16-bit value that the assembler will // convert back to 8-bit or 16-bit. In any event, the bank byte is never @@ -781,7 +790,7 @@ namespace SourceGen { // ((label >> rightShift) & mask) [+ adj] if (rightShift != 0 || needMask) { - if (flags != FormatNumericOpFlags.HasHashPrefix) { + if ((flags & FormatNumericOpFlags.HasHashPrefix) == 0) { sb.Append("0+"); } if (rightShift != 0 && needMask) { @@ -836,6 +845,10 @@ namespace SourceGen { if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; } + // TODO(xyzzy): non-unique prefix + if ((flags & FormatNumericOpFlags.StripAnnotation) == 0) { + symLabel = Symbol.AppendAnnotation(symLabel, sym.LabelAnno); + } if (operandLen == 1) { // Use the byte-selection operator to get the right piece. @@ -872,7 +885,7 @@ namespace SourceGen { shOp = ""; } - if (flags == FormatNumericOpFlags.IsPcRel) { + if ((flags & FormatNumericOpFlags.IsPcRel) != 0) { // PC-relative operands are funny, because an 8- or 16-bit value is always // expanded to 24 bits. We output a 16-bit value that the assembler will // convert back to 8-bit or 16-bit. In any event, the bank byte is never @@ -929,6 +942,10 @@ namespace SourceGen { if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; } + // TODO(xyzzy): non-unique prefix + if ((flags & FormatNumericOpFlags.StripAnnotation) == 0) { + symLabel = Symbol.AppendAnnotation(symLabel, sym.LabelAnno); + } int adjustment; diff --git a/SourceGen/RuntimeData/Help/index.html b/SourceGen/RuntimeData/Help/index.html index 45e5157..a35b901 100644 --- a/SourceGen/RuntimeData/Help/index.html +++ b/SourceGen/RuntimeData/Help/index.html @@ -35,7 +35,8 @@ and 65816 code. The official web site is
  • SourceGen Concepts
  • All About Symbols
      -
    • Local Variables
    • +
    • Internal Address Symbols
    • +
    • External Address Symbols
    • Weak References
    • Parts and Adjustments
    • Automatic Use of Nearby Targets
    • diff --git a/SourceGen/RuntimeData/Help/intro.html b/SourceGen/RuntimeData/Help/intro.html index a2abe59..27915b8 100644 --- a/SourceGen/RuntimeData/Help/intro.html +++ b/SourceGen/RuntimeData/Help/intro.html @@ -66,7 +66,7 @@ rest of the documentation assumes you've read and understood this.

      instruction set and assembly-language programming, but disassembling other programs is actually a pretty good way to learn how to code in assembly. You will need to be familiar with hexadecimal numbers and -general programming concepts to make sense of anything, however.

      +general programming concepts to make sense of this, however.

      About 6502 Code

      @@ -407,7 +407,7 @@ by splitting formatted data into sub-regions at label boundaries.

      All About Symbols

      A symbol has two basic parts, a label and a value. The label is a short -ASCII string; the value may be an 8-to-24-bit address or a numeric +ASCII string; the value may be an 8-to-24-bit address or a 32-bit numeric constant. Symbols can be defined in different ways, and applied in different ways.

      @@ -420,6 +420,11 @@ with most assemblers:

    Label comparisons are case-sensitive, as is customary for programming languages.

    +

    Sometimes the purpose of a subroutine or variable isn't immediately +clear, but you can take a reasonable guess. You can document your +uncertainty by adding a question mark ('?') to the end of the label. +This isn't really part of the label, so it won't appear in the assembled +output, and you don't have to include it when searching for a symbol.

    Some assemblers restrict the set of valid labels further. For example, 64tass uses a leading underscore to indicate a local label, and reserves a double leading underscore (e.g. __label) for its own @@ -437,7 +442,46 @@ MYSTRING .STR "hello"

    See Parts and Adjustments for more details.

    -

    Symbol Types

    +

    Symbols that represent a memory address within a project are treated +differently from those outside a project. We refer to these as internal +and external addresses, respectively.

    + + +

    Internal Address Symbols

    + +

    Symbols that represent an address inside the file being disassembled +are referred to as internal. They come in two varieties.

    + +

    User labels are labels added to instructions or data by the user. +The editor will try to prevent you from creating a label that has the same +name as another symbol, but if you manage to do so, the user label takes +precedence over symbols from other sources. User labels may be tagged +as local, global, or global and exported. Local vs. global is important +for the label localizer, while exported symbols can be pulled directly +into other projects.

    + +

    Auto labels are automatically generated labels placed on +instructions or data offsets that are the target of operands. They're +formed by appending the hexadecimal address to the letter "L", with +additional characters added if some other symbol has already defined +that label. Options can be set that change the "L" to a character or +characters based on how the label is referenced, e.g. "B" for branch targets. +Auto labels are only added where they are needed, and are removed when +no longer necessary. Because auto labels may be renamed or vanish, the +editor will try to prevent you from referring to them explicitly when +editing operands.

    + + +

    External Address Symbols

    + +

    Symbols that represent an address outside the file being disassembled +are referred to as external. These may be ROM entry points, +data buffers, zero-page variables, or a number of other things. Because +the memory address they appear at aren't within the bounds of the file, +we can't simply put an address label on them. Three different mechanisms +exist for defining them. If an instruction or data operand refers to +an address outside the file bounds, SourceGen looks for a symbol with +a matching address value.

    Platform symbols are defined in platform symbol files. These are named with a ".sym65" extension, and have a fairly straightforward @@ -445,12 +489,9 @@ name/value syntax. Several files for popular platforms come with SourceGen and live in the RuntimeData directory. You can also create your own, but they have to live in the same directory as the project file.

    -

    Platform symbols can be addresses or constants. If an instruction -or data operand references an address outside the scope of the data -file, SourceGen looks for a symbol with a matching address value. If -it finds one, it automatically uses that symbol. Symbolic constants -can be used the same way, but are not matched automatically. This makes -them useful for things like operating system function numbers.

    +

    Platform symbols can be addresses or constants. Addresses are +limited to 24-bit values, and are matched automatically. Constants may +be 32-bit values, but must be specified manually.

    If two platform symbols have the same label, only the most recently read one is kept. If two platform symbols have different labels but the @@ -477,9 +518,10 @@ to define two different symbols, and have the correct one applied based on the access type.

    Project symbols behave like platform symbols, but they are -defined in the project file itself. The editor will prevent you from -creating two symbols with the same name. If two symbols have the same -value, the one whose label comes first alphabetically is used.

    +defined in the project file itself, through the Project Properties editor. +The editor will try to prevent you from creating two symbols with the same +name. If two symbols have the same value, the one whose label comes +first alphabetically is used.

    Project symbols always have precedence over platform symbols, allowing you to redefine symbols within a project. (You can "hide" a platform @@ -487,29 +529,12 @@ symbol by creating a project symbol constant with the same name. Use a value like $ffffffff or $deadbeef so you'll know why it's there.)

    Local variables are redefinable symbols that are organized -into tables. They're used to specify labels for zero-page and 65816 -stack-relative instructions.

    - -

    User labels are labels added to instructions or data by the user. -The editor won't allow you to add a label that conflicts, but if you -manage to do so, the user label takes precedence over project and platform -symbols. User labels may be tagged as local, global, or global and -exported. Local vs. global is important for the label localizer, while -exported symbols can be pulled directly into other projects.

    - -

    Auto labels are automatically generated labels placed on -instructions or data offsets that are the target of operands. They're -formed by appending the hexadecimal address to the letter "L", with -additional characters added if some other symbol has already defined -that label. Options can be set that change the "L" to a character or -characters based on how the label is referenced, e.g. "B" for branch targets. -Auto labels are only added where they are needed, and are removed when -no longer necessary. Because auto labels may be renamed or vanish, the -editor will try to prevent you from referring to them when editing -operands.

    +into tables. They're used to specify labels for zero-page addresses +and 65816 stack-relative instructions. These are explained in more +detail in the next section.

    -

    Local Variables

    +

    How Local Variables Work

    Local variables are applied to instructions that have zero page operands (op ZP, op (ZP),Y, etc.), or @@ -524,7 +549,7 @@ example:

              LDA     ($00),Y
              INC     $02
    -         ... later ...
    +         ... elsewhere ...
              DEC     $00
              STA     ($01),Y
     
    diff --git a/SourceGen/RuntimeData/Help/tutorials.html b/SourceGen/RuntimeData/Help/tutorials.html index 9389b0b..b52ca9f 100644 --- a/SourceGen/RuntimeData/Help/tutorials.html +++ b/SourceGen/RuntimeData/Help/tutorials.html @@ -416,13 +416,16 @@ values into direct page address $02/03. This appears to be setting up a pointer to $2063, which is a data area inside the file. So let's make it official.

    Select the line at address $2063, and use Actions > Edit Label to -give it the label "XDATA". Now edit the operand on line $203d, and set it -to the symbol "XDATA", with the part "low". Edit the operand on line $2041, -and set it to "XDATA" with the part "high". (Note the symbol text box +give it the label "XDATA?". The question mark on the end is there to +remind us that we're not entirely sure what this is. Now edit the +operand on line $203d, and set it to the symbol "XDATA", with the part +"low". The question mark isn't really part of the label, so you don't +need to type it here. Edit the operand on line $2041, +and set it to "XDATA" with the part "high". (The symbol text box gets focus immediately, so you can start typing the symbol name as soon as the dialog opens; you don't need to click around first.) If all -went well, the operands should now read LDA #<XDATA -and LDA #>XDATA.

    +went well, the operands should now read LDA #<XDATA? +and LDA #>XDATA?.

    Let's give the pointer a name. Select line $203d, and use Actions > Create Local Variable Table to create an empty table. Click "New Symbol" on the right side. Leave the Address button selected. diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index a23cfe3..cc4c857 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -21,6 +21,8 @@ namespace SourceGen { /// Symbolic representation of a value. Instances are immutable. ///

  • public class Symbol { + public const char UNCERTAIN_CHAR = '?'; + /// /// How was the symbol defined? /// @@ -29,26 +31,84 @@ namespace SourceGen { // looking up a symbol by value from the symbol table, because multiple symbols // can have the same value. Unknown = 0, - User, // user-defined label - Project, // from project configuration file - Platform, // from platform definition file - Auto, // auto-generated label - Variable // local variable + User, // user-defined; only used for internal address labels + Project, // external address or const, from project configuration file + Platform, // external address or const, from platform definition file + Auto, // auto-generated internal address label + Variable // external address or const, from local variable table } /// - /// Local internal label, global internal label, or reference to an - /// external address? Constants get a separate type. + /// Unique or non-unique address label? Is it required to be global or exported? + /// Constants get a separate type. /// public enum Type { Unknown = 0, - LocalOrGlobalAddr, // local symbol, may be promoted to global - GlobalAddr, // user wants this to be a global symbol - GlobalAddrExport, // global symbol that is exported to linkers + + NonUniqueLocalAddr, // non-unique local symbol, may be promoted to global + LocalOrGlobalAddr, // unique local symbol, may be promoted to global + GlobalAddr, // unique global symbol + GlobalAddrExport, // unique global symbol; included in linker export table + ExternalAddr, // reference to address outside program (e.g. platform sym file) Constant // constant value } + /// + /// User-specified commentary on the label. + /// + public enum LabelAnnotation { + None = 0, + Uncertain, // user isn't sure if this is correct + Generated // label was generated, e.g. address table formatter + } + + /// + /// Unique label. + /// + /// + /// Non-unique labels have extra stuff at the end to make them unique. That is + /// included here, so that the Label field is still viable as a unique identifier. + /// + public string Label { get; private set; } + + /// + /// Symbol's 32-bit numeric value. + /// + /// + /// For address types, the value should be constained to [0,2^24). For constants, + /// all values are valid. + /// + public int Value { get; private set; } + + /// + /// Symbol origin, e.g. auto-generated or entered by user. Enum values are in + /// priority order. + /// + public Source SymbolSource { get; private set; } + + /// + /// Type of symbol, e.g. local or global. + /// + public Type SymbolType { get; private set; } + + /// + /// Notes on the label. + /// + public LabelAnnotation LabelAnno { get; private set; } + + /// + /// Two-character string representation of Source and Type, for display in the UI. + /// Generated from SymbolSource and SymbolType. + /// + public string SourceTypeString { get; private set; } + + /// + /// Label with annotations. Generated from Label and LabelAnno. + /// + public string AnnotatedLabel { get; private set; } + + /// /// True if the symbol's type is an internal label (auto or user). Will be false /// for external addresses (including variables) and constants. @@ -64,80 +124,131 @@ namespace SourceGen { get { return SymbolSource == Source.Variable; } } + /// + /// True if the symbol represents a constant value. + /// public bool IsConstant { get { return SymbolType == Type.Constant; } } - /// - /// Label sent to assembler. - /// - public string Label { get; private set; } - - /// - /// Symbol's numeric value. - /// - public int Value { get; private set; } - - /// - /// Symbol origin, e.g. auto-generated or entered by user. Enum values are in - /// priority order. - /// - public Source SymbolSource { get; private set; } - - /// - /// Type of symbol, e.g. local or global. - /// - public Type SymbolType { get; private set; } - - /// - /// Two-character string representation of Source and Type, for display in the UI. - /// - public string SourceTypeString { get; private set; } - - // No nullary constructor. private Symbol() { } /// - /// Constructs immutable object. + /// Basic constructor. /// /// Label string. Syntax assumed valid. /// Symbol value. /// User-defined, auto-generated, ? /// Type of symbol this is. - public Symbol(string label, int value, Source source, Type type) { - Debug.Assert(!string.IsNullOrEmpty(label)); + public Symbol(string label, int value, Source source, Type type, + LabelAnnotation labelAnno) { + Debug.Assert(Asm65.Label.ValidateLabel(label)); + Debug.Assert(type != Type.NonUniqueLocalAddr); Label = label; Value = value; SymbolType = type; SymbolSource = source; + LabelAnno = labelAnno; // Generate SourceTypeString. - string sts; + char sc, tc; switch (SymbolSource) { - case Source.Auto: sts = "A"; break; - case Source.User: sts = "U"; break; - case Source.Platform: sts = "P"; break; - case Source.Project: sts = "R"; break; - case Source.Variable: sts = "V"; break; - default: sts = "?"; break; + case Source.Auto: sc = 'A'; break; + case Source.User: sc = 'U'; break; + case Source.Platform: sc = 'P'; break; + case Source.Project: sc = 'J'; break; + case Source.Variable: sc = 'V'; break; + default: sc = '?'; break; } switch (SymbolType) { - case Type.LocalOrGlobalAddr: sts += "L"; break; - case Type.GlobalAddr: sts += "G"; break; - case Type.GlobalAddrExport: sts += "X"; break; - case Type.ExternalAddr: sts += "E"; break; - case Type.Constant: sts += "C"; break; - default: sts += "?"; break; + case Type.NonUniqueLocalAddr: tc = 'N'; break; + case Type.LocalOrGlobalAddr: tc = 'L'; break; + case Type.GlobalAddr: tc = 'G'; break; + case Type.GlobalAddrExport: tc = 'X'; break; + case Type.ExternalAddr: tc = 'E'; break; + case Type.Constant: tc = 'C'; break; + default: tc = '?'; break; + } + SourceTypeString = "" + sc + tc; + + // Generate AnnotatedLabel. + AnnotatedLabel = AppendAnnotation(Label, LabelAnno); + } + + /// + /// Constructor for non-unique labels. + /// + /// + /// + /// + /// + /// + /// + public Symbol(string label, int value, Source source, Type type, + LabelAnnotation labelAnno, int offset) + : this(label, value, source, type, labelAnno) { + Debug.Assert(false); // TODO(xyzzy) + } + + /// + /// Performs a detailed validation of a symbol label, breaking out different failure + /// causes for the benefit of code that reports errors to the user. The label may + /// have additional characters, such as annotations, which are trimmed away. The + /// trimmed version of the string is returned. + /// + /// Label to examine. + /// True if the entire label is valid. + /// True if the label has a valid length. + /// True if the first character is valid. + /// Annotation found, or None if none found. + /// Trimmed version of the string. + public static string TrimAndValidateLabel(string label, out bool isValid, + out bool isLenValid, out bool isFirstCharValid, out LabelAnnotation anno) { + anno = LabelAnnotation.None; + + // Do we have at least one char? + if (string.IsNullOrEmpty(label)) { + isValid = isLenValid = isFirstCharValid = false; + return label; + } + + string trimLabel = label; + // Check for an annotation char, remove it if found. + if (trimLabel[trimLabel.Length - 1] == UNCERTAIN_CHAR) { + anno = LabelAnnotation.Uncertain; + trimLabel = trimLabel.Substring(0, trimLabel.Length - 1); + } + + // Now that we're down to the base string, do the full validation test. If it + // passes, we don't need to dig any deeper. + isValid = Asm65.Label.ValidateLabelDetail(trimLabel, out isLenValid, + out isFirstCharValid); + + return trimLabel; + } + + /// + /// Augments a label string with an annotation identifier. + /// + /// String to augment. + /// Annotation; may be None. + /// Original or updated string. + public static string AppendAnnotation(string label, LabelAnnotation anno) { + if (anno == LabelAnnotation.Uncertain) { + return label + UNCERTAIN_CHAR; + //} else if (anno == LabelAnnotation.Generated) { + // return label + '\u00a4'; // CURRENCY_SIGN 'ยค' + } else { + return label; } - SourceTypeString = sts; } public override string ToString() { return Label + "{" + SymbolSource + "," + SymbolType + - ",val=$" + Value.ToString("x4") + "}"; + ",val=$" + Value.ToString("x4") + "," + LabelAnno + "}"; } public static bool operator ==(Symbol a, Symbol b) { @@ -147,10 +258,11 @@ namespace SourceGen { if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { return false; // one is null } - // All fields must be equal. Ignore SourceTypeString, since it's generated - // from Source and Type. + // All fields must be equal. Ignore SourceTypeString and AnnotatedLabel, since + // they're generated from other fields. return Asm65.Label.LABEL_COMPARER.Equals(a.Label, b.Label) && a.Value == b.Value && - a.SymbolSource == b.SymbolSource && a.SymbolType == b.SymbolType; + a.SymbolSource == b.SymbolSource && a.SymbolType == b.SymbolType && + a.LabelAnno == b.LabelAnno; } public static bool operator !=(Symbol a, Symbol b) { return !(a == b); @@ -159,10 +271,10 @@ namespace SourceGen { return obj is Symbol && this == (Symbol)obj; } public override int GetHashCode() { - // Convert the label to upper case before computing the hash code, so that - // symbols with "foo" and "FOO" (which are equal) have the same hash code. + // Convert label to "normal form" if we're doing case-insensitive. (We're not + // anymore, so it's a no-op now.) return Asm65.Label.ToNormal(Label).GetHashCode() ^ - Value ^ (int)SymbolType ^ (int)SymbolSource; + Value ^ (int)SymbolType ^ (int)SymbolSource ^ (int)LabelAnno; } diff --git a/SourceGen/WpfGui/EditDefSymbol.xaml.cs b/SourceGen/WpfGui/EditDefSymbol.xaml.cs index 2f1bbdb..3fb307b 100644 --- a/SourceGen/WpfGui/EditDefSymbol.xaml.cs +++ b/SourceGen/WpfGui/EditDefSymbol.xaml.cs @@ -208,7 +208,7 @@ namespace SourceGen.WpfGui { mDefaultLabelColor = labelNotesLabel.Foreground; if (mOldSym != null) { - Label = mOldSym.Label; + Label = mOldSym.AnnotatedLabel; Value = mNumFormatter.FormatValueInBase(mOldSym.Value, mOldSym.DataDescriptor.NumBase); if (mOldSym.HasWidth) { @@ -251,11 +251,12 @@ namespace SourceGen.WpfGui { // Label must be valid and not already exist in the table we're editing. (For project // symbols, it's okay if an identical label exists elsewhere.) - bool labelValid = Asm65.Label.ValidateLabel(Label); + string trimLabel = Symbol.TrimAndValidateLabel(Label, out bool labelValid, + out bool unused1, out bool unused2, out Symbol.LabelAnnotation unused3); bool labelUnique; // NOTE: should be using Asm65.Label.LABEL_COMPARER? - if (mDefSymbolList.TryGetValue(Label, out DefSymbol existing)) { + if (mDefSymbolList.TryGetValue(trimLabel, out DefSymbol existing)) { // It's okay if it's the same object. labelUnique = (existing == mOldSym); } else { @@ -264,7 +265,7 @@ namespace SourceGen.WpfGui { // For local variables, do a secondary uniqueness check across the full symbol table. if (labelUnique && mSymbolTable != null) { - labelUnique = !mSymbolTable.TryGetValue(Label, out Symbol sym); + labelUnique = !mSymbolTable.TryGetValue(trimLabel, out Symbol sym); // It's okay if this and the other are both variables. if (!labelUnique && IsVariable && sym.IsVariable) { @@ -275,7 +276,7 @@ namespace SourceGen.WpfGui { // Value must be blank, meaning "erase any earlier definition", or valid value. // (Hmm... don't currently have a way to specify "no symbol" in DefSymbol.) //if (!string.IsNullOrEmpty(valueTextBox.Text)) { - bool valueValid = ParseValue(out int thisValue, out int unused2); + bool valueValid = ParseValue(out int thisValue, out int unused4); //} else { // valueValid = true; //} @@ -382,9 +383,12 @@ namespace SourceGen.WpfGui { direction = DefSymbol.DirectionFlags.None; } - NewSym = new DefSymbol(Label, value, + // Parse and strip the annotation. + string trimLabel = Symbol.TrimAndValidateLabel(Label, out bool unused1, + out bool unused2, out bool unused3, out Symbol.LabelAnnotation anno); + NewSym = new DefSymbol(trimLabel, value, IsVariable ? Symbol.Source.Variable : Symbol.Source.Project, - IsConstant ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, + IsConstant ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, anno, subType, width, width > 0, Comment, direction, null, string.Empty); DialogResult = true; diff --git a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs index 8f5e96d..fd742fe 100644 --- a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs +++ b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs @@ -1144,9 +1144,9 @@ namespace SourceGen.WpfGui { // an empty name. We don't really need to create something unique since the // dialog will handle it. initialVar = new DefSymbol("VAR", mOperandValue, - Symbol.Source.Variable, symType, FormatDescriptor.SubType.None, - 1, true, string.Empty, DefSymbol.DirectionFlags.ReadWrite, - null, string.Empty); + Symbol.Source.Variable, symType, Symbol.LabelAnnotation.None, + FormatDescriptor.SubType.None, 1, true, string.Empty, + DefSymbol.DirectionFlags.ReadWrite, null, string.Empty); } EditDefSymbol dlg = new EditDefSymbol(this, mFormatter, diff --git a/SourceGen/WpfGui/EditLabel.xaml.cs b/SourceGen/WpfGui/EditLabel.xaml.cs index 676bb1f..3e669da 100644 --- a/SourceGen/WpfGui/EditLabel.xaml.cs +++ b/SourceGen/WpfGui/EditLabel.xaml.cs @@ -93,8 +93,11 @@ namespace SourceGen.WpfGui { LabelText = string.Empty; radioButtonLocal.IsChecked = true; } else { - LabelText = LabelSym.Label; + LabelText = LabelSym.AnnotatedLabel; switch (LabelSym.SymbolType) { + case Symbol.Type.NonUniqueLocalAddr: + Debug.Assert(false); // TODO(xyzzy) + break; case Symbol.Type.LocalOrGlobalAddr: radioButtonLocal.IsChecked = true; break; @@ -118,6 +121,7 @@ namespace SourceGen.WpfGui { } private void LabelTextBox_TextChanged() { +#if false string str = LabelText; bool valid = true; @@ -153,6 +157,28 @@ namespace SourceGen.WpfGui { } else { firstLetterLabel.Foreground = mDefaultLabelColor; } +#endif + bool isBlank = (LabelText.Length == 0); + + string trimLabel = Symbol.TrimAndValidateLabel(LabelText, out bool isValid, + out bool isLenValid, out bool isFirstCharValid, out Symbol.LabelAnnotation anno); + if (isBlank || isLenValid) { + maxLengthLabel.Foreground = mDefaultLabelColor; + } else { + maxLengthLabel.Foreground = Brushes.Red; + } + if (isBlank || isFirstCharValid) { + firstLetterLabel.Foreground = mDefaultLabelColor; + } else { + firstLetterLabel.Foreground = Brushes.Red; + } + if (isBlank || isValid) { + // TODO(maybe): if the problem is that the label starts with a number, we + // shouldn't light up this (which is the "valid chars are" label) as well. + validCharsLabel.Foreground = mDefaultLabelColor; + } else { + validCharsLabel.Foreground = Brushes.Red; + } // Refuse to continue if the label already exists. The only exception is if // it's the same symbol, and it's user-defined. (If they're trying to edit an @@ -162,15 +188,15 @@ namespace SourceGen.WpfGui { // where a label is being renamed from "FOO" to "Foo". We should be able to // test for object equality on the Symbol to determine if we're renaming a // symbol to itself. - if (valid && mSymbolTable.TryGetValue(str, out Symbol sym) && + if (isValid && mSymbolTable.TryGetValue(trimLabel, out Symbol sym) && (sym != LabelSym || LabelSym.SymbolSource != Symbol.Source.User)) { - valid = false; + isValid = false; notDuplicateLabel.Foreground = Brushes.Red; } else { notDuplicateLabel.Foreground = mDefaultLabelColor; } - IsValid = valid; + IsValid = isBlank || isValid; } private void OkButton_Click(object sender, RoutedEventArgs e) { @@ -178,6 +204,7 @@ namespace SourceGen.WpfGui { LabelSym = null; } else { Symbol.Type symbolType; + // TODO(xyzzy): non-local if (radioButtonLocal.IsChecked == true) { symbolType = Symbol.Type.LocalOrGlobalAddr; } else if (radioButtonGlobal.IsChecked == true) { @@ -188,7 +215,12 @@ namespace SourceGen.WpfGui { Debug.Assert(false); // WTF symbolType = Symbol.Type.LocalOrGlobalAddr; } - LabelSym = new Symbol(LabelText, mAddress, Symbol.Source.User, symbolType); + + // Parse and strip the annotation. + string trimLabel = Symbol.TrimAndValidateLabel(LabelText, out bool unused1, + out bool unused2, out bool unused3, out Symbol.LabelAnnotation anno); + + LabelSym = new Symbol(trimLabel, mAddress, Symbol.Source.User, symbolType, anno); } DialogResult = true; } diff --git a/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs b/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs index 8166189..1f55e79 100644 --- a/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs +++ b/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs @@ -51,18 +51,17 @@ namespace SourceGen.WpfGui { public int Width { get; private set; } public string Comment { get; private set; } - // Numeric form of Value, used so we can sort hex/dec/binary correctly. - public int NumericValue { get; private set; } + public DefSymbol DefSym; - public FormattedSymbol(string label, int numericValue, string value, string type, - int width, string comment) { + public FormattedSymbol(DefSymbol defSym, string label, string value, + string type, int width, string comment) { Label = label; Value = value; Type = type; Width = width; Comment = comment; - NumericValue = numericValue; + DefSym = defSym; } } @@ -175,8 +174,8 @@ namespace SourceGen.WpfGui { } FormattedSymbol fsym = new FormattedSymbol( - defSym.Label, - defSym.Value, + defSym, + defSym.AnnotatedLabel, mFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase), typeStr, defSym.DataDescriptor.Length, @@ -236,7 +235,7 @@ namespace SourceGen.WpfGui { cmp = string.Compare(fsym1.Label, fsym2.Label); break; case SortField.Value: - cmp = fsym1.NumericValue - fsym2.NumericValue; + cmp = fsym1.DefSym.Value - fsym2.DefSym.Value; break; case SortField.Type: cmp = string.Compare(fsym1.Type, fsym2.Type); @@ -303,8 +302,7 @@ namespace SourceGen.WpfGui { return; } FormattedSymbol item = (FormattedSymbol)objItem; - DefSymbol defSym = mWorkTable.GetByLabel(item.Label); - DoEditSymbol(defSym); + DoEditSymbol(item.DefSym); } private void NewSymbolButton_Click(object sender, RoutedEventArgs e) { @@ -329,9 +327,7 @@ namespace SourceGen.WpfGui { // Single-select list view, button dimmed when no selection. Debug.Assert(symbolsList.SelectedItems.Count == 1); FormattedSymbol item = (FormattedSymbol)symbolsList.SelectedItems[0]; - DefSymbol defSym = mWorkTable.GetByLabel(item.Label); - Debug.Assert(defSym != null); - DoEditSymbol(defSym); + DoEditSymbol(item.DefSym); } private void DoEditSymbol(DefSymbol defSym) { @@ -345,7 +341,7 @@ namespace SourceGen.WpfGui { // Replace entry in items source. for (int i = 0; i < Variables.Count; i++) { - if (Variables[i].Label.Equals(defSym.Label)) { + if (Variables[i].DefSym == defSym) { Variables[i] = CreateFormattedSymbol(dlg.NewSym); break; } @@ -361,9 +357,10 @@ namespace SourceGen.WpfGui { int selectionIndex = symbolsList.SelectedIndex; FormattedSymbol item = (FormattedSymbol)symbolsList.SelectedItems[0]; - mWorkTable.RemoveByLabel(item.Label); + DefSymbol defSym = item.DefSym; + mWorkTable.RemoveByLabel(defSym.Label); for (int i = 0; i < Variables.Count; i++) { - if (Variables[i].Label.Equals(item.Label)) { + if (Variables[i].DefSym == defSym) { Variables.RemoveAt(i); break; } diff --git a/SourceGen/WpfGui/EditProjectProperties.xaml.cs b/SourceGen/WpfGui/EditProjectProperties.xaml.cs index 07cf2c7..d7056c7 100644 --- a/SourceGen/WpfGui/EditProjectProperties.xaml.cs +++ b/SourceGen/WpfGui/EditProjectProperties.xaml.cs @@ -431,20 +431,16 @@ namespace SourceGen.WpfGui { public string Width { get; private set; } public string Comment { get; private set; } - // Numeric form of Value, used so we can sort hex/dec/binary correctly. - public int NumericValue { get; private set; } - public int NumericWidth { get; private set; } + public DefSymbol DefSym { get; private set; } - public FormattedSymbol(string label, int numericValue, string value, string type, - int numericWidth, string width, string comment) { + public FormattedSymbol(DefSymbol defSym, string label, string value, + string type, string width, string comment) { + DefSym = defSym; Label = label; Value = value; Type = type; Width = width; Comment = comment; - - NumericValue = numericValue; - NumericWidth = numericWidth; } } public ObservableCollection ProjectSymbols { get; private set; } = @@ -488,11 +484,10 @@ namespace SourceGen.WpfGui { } FormattedSymbol fsym = new FormattedSymbol( - defSym.Label, - defSym.Value, + defSym, + defSym.AnnotatedLabel, mFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase), typeStr, - defSym.DataDescriptor.Length, defSym.HasWidth ? defSym.DataDescriptor.Length.ToString() : NO_WIDTH_STR, defSym.Comment); return fsym; @@ -546,7 +541,7 @@ namespace SourceGen.WpfGui { cmp = string.Compare(fsym1.Label, fsym2.Label); break; case SortField.Value: - cmp = fsym1.NumericValue - fsym2.NumericValue; + cmp = fsym1.DefSym.Value - fsym2.DefSym.Value; break; case SortField.Type: cmp = string.Compare(fsym1.Type, fsym2.Type); @@ -561,7 +556,8 @@ namespace SourceGen.WpfGui { } else if (fsym2.Width == NO_WIDTH_STR) { cmp = 1; } else { - cmp = fsym1.NumericWidth - fsym2.NumericWidth; + cmp = fsym1.DefSym.DataDescriptor.Length - + fsym2.DefSym.DataDescriptor.Length; } break; case SortField.Comment: @@ -605,8 +601,7 @@ namespace SourceGen.WpfGui { // Single-select list view, button dimmed when no selection. Debug.Assert(projectSymbolsList.SelectedItems.Count == 1); FormattedSymbol item = (FormattedSymbol)projectSymbolsList.SelectedItems[0]; - DefSymbol defSym = mWorkProps.ProjectSyms[item.Label]; - DoEditSymbol(defSym); + DoEditSymbol(item.DefSym); } private void ProjectSymbolsList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { @@ -616,8 +611,7 @@ namespace SourceGen.WpfGui { return; } FormattedSymbol item = (FormattedSymbol)objItem; - DefSymbol defSym = mWorkProps.ProjectSyms[item.Label]; - DoEditSymbol(defSym); + DoEditSymbol(item.DefSym); } private void DoEditSymbol(DefSymbol defSym) { @@ -631,12 +625,14 @@ namespace SourceGen.WpfGui { IsDirty = true; // Replace entry in items source. - for (int i = 0; i < ProjectSymbols.Count; i++) { - if (ProjectSymbols[i].Label.Equals(defSym.Label)) { + int i; + for (i = 0; i < ProjectSymbols.Count; i++) { + if (ProjectSymbols[i].DefSym == defSym) { ProjectSymbols[i] = CreateFormattedSymbol(dlg.NewSym); break; } } + Debug.Assert(i != ProjectSymbols.Count); UpdateControls(); okButton.Focus(); @@ -649,12 +645,12 @@ namespace SourceGen.WpfGui { int selectionIndex = projectSymbolsList.SelectedIndex; FormattedSymbol item = (FormattedSymbol)projectSymbolsList.SelectedItems[0]; - DefSymbol defSym = mWorkProps.ProjectSyms[item.Label]; + DefSymbol defSym = item.DefSym; mWorkProps.ProjectSyms.Remove(defSym.Label); IsDirty = true; for (int i = 0; i < ProjectSymbols.Count; i++) { - if (ProjectSymbols[i].Label.Equals(item.Label)) { + if (ProjectSymbols[i].DefSym == defSym) { ProjectSymbols.RemoveAt(i); break; } diff --git a/SourceGen/WpfGui/FormatAddressTable.xaml.cs b/SourceGen/WpfGui/FormatAddressTable.xaml.cs index 5b2617a..b5b6177 100644 --- a/SourceGen/WpfGui/FormatAddressTable.xaml.cs +++ b/SourceGen/WpfGui/FormatAddressTable.xaml.cs @@ -500,7 +500,7 @@ namespace SourceGen.WpfGui { mProject.SymbolTable, "T"); // tmpSym was returned as an auto-label, make it a user label instead tmpSym = new Symbol(tmpSym.Label, tmpSym.Value, Symbol.Source.User, - Symbol.Type.LocalOrGlobalAddr); + Symbol.Type.LocalOrGlobalAddr, Symbol.LabelAnnotation.Generated); newLabels[targetOffset] = tmpSym; // overwrites previous targetLabel = tmpSym.Label; AddPreviewItem(addr, targetOffset, "(+) " + targetLabel);