diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index ac46ec7..3453eff 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -37,9 +37,9 @@ namespace Asm65 { /// because it guarantees that a given Formatter object will produce the same number of /// lines of output. /// - /// NOTE: if the CpuDef changes, the cached values in the Formatter will become invalid. - /// Discard the Formatter and create a new one. (This could be fixed by keying off of - /// the OpDef instead of OpDef.Opcode, but that's less convenient.) + /// NOTE: if the CpuDef changes, the cached values in the Formatter will become invalid + /// (e.g. mOpcodeStrings). Discard the Formatter and create a new one. (This could be + /// fixed by keying off of the OpDef instead of OpDef.Opcode, but that's less convenient.) /// public class Formatter { /// @@ -54,12 +54,13 @@ namespace Asm65 { public bool mUpperOperandA; // display acc operand in upper case? public bool mUpperOperandS; // display stack operand in upper case? public bool mUpperOperandXY; // display index register operand in upper case? - public bool mBankSelectBackQuote; // use '`' rather than '^' for bank select? + public bool mAddSpaceLongComment; // insert space after delimiter for long comments? // functional changes to assembly output public bool mSuppressHexNotation; // omit '$' before hex digits public bool mSuppressImpliedAcc; // emit just "LSR" rather than "LSR A"? + public bool mBankSelectBackQuote; // use '`' rather than '^' for bank selector? public string mForceDirectOperandPrefix; // these may be null or empty public string mForceAbsOpcodeSuffix; @@ -68,7 +69,8 @@ namespace Asm65 { public string mForceLongOpcodeSuffix; public string mForceLongOperandPrefix; - public string mLocalVariableLablePrefix; // Merlin 32 puts ']' before var names + public string mLocalVariableLabelPrefix; // e.g. Merlin 32 puts ']' before var names + public string mNonUniqueLabelPrefix; // e.g. ':' or '@' before local label public string mEndOfLineCommentDelimiter; // usually ';' public string mFullLineCommentDelimiterBase; // usually ';' or '*', WITHOUT extra space @@ -291,7 +293,7 @@ namespace Asm65 { /// public FormatConfig Config { get { return mFormatConfig; } } - // Bits and pieces. + // Cached bits and pieces. char mHexFmtChar; string mHexPrefix; string mAccChar; @@ -365,6 +367,13 @@ namespace Asm65 { get { return mFormatConfig.mBoxLineCommentDelimiter; } } + /// + /// Prefix for non-unique address labels. + /// + public string NonUniqueLabelPrefix { + get { return mFormatConfig.mNonUniqueLabelPrefix; } + } + /// /// When formatting a symbol with an offset, if this flag is set, generate code that /// assumes the assembler applies the adjustment, then shifts the result. If not, @@ -391,6 +400,10 @@ namespace Asm65 { mFormatConfig.mBoxLineCommentDelimiter = string.Empty; } + if (string.IsNullOrEmpty(mFormatConfig.mNonUniqueLabelPrefix)) { + mFormatConfig.mNonUniqueLabelPrefix = "@"; + } + if (mFormatConfig.mAddSpaceLongComment) { mFullLineCommentDelimiterPlus = mFormatConfig.mFullLineCommentDelimiterBase + " "; } else { @@ -617,8 +630,8 @@ namespace Asm65 { /// specified. /// public string FormatVariableLabel(string label) { - if (!string.IsNullOrEmpty(mFormatConfig.mLocalVariableLablePrefix)) { - return mFormatConfig.mLocalVariableLablePrefix + label; + if (!string.IsNullOrEmpty(mFormatConfig.mLocalVariableLabelPrefix)) { + return mFormatConfig.mLocalVariableLabelPrefix + label; } else { return label; } diff --git a/SourceGen/AppSettings.cs b/SourceGen/AppSettings.cs index 1ac0013..2f34016 100644 --- a/SourceGen/AppSettings.cs +++ b/SourceGen/AppSettings.cs @@ -66,6 +66,7 @@ namespace SourceGen { public const string FMT_PSEUDO_OP_NAMES = "fmt-pseudo-op-names"; public const string FMT_CHAR_DELIM = "fmt-char-delim"; public const string FMT_STRING_DELIM = "fmt-string-delim"; + public const string FMT_NON_UNIQUE_LABEL_PREFIX = "fmt-non-unique-label-prefix"; public const string FMT_LOCAL_VARIABLE_PREFIX = "fmt-local-variable-prefix"; public const string CLIP_LINE_FORMAT = "clip-line-format"; diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index 3f860a5..eaf1a80 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -190,10 +190,11 @@ namespace SourceGen.AsmGen { config.mForceDirectOperandPrefix = string.Empty; config.mForceAbsOperandPrefix = string.Empty; config.mForceLongOperandPrefix = string.Empty; - config.mLocalVariableLablePrefix = "."; + config.mLocalVariableLabelPrefix = "."; config.mEndOfLineCommentDelimiter = ";"; config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; + config.mNonUniqueLabelPrefix = "@"; config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Common; Formatter.DelimiterSet charSet = new Formatter.DelimiterSet(); @@ -387,7 +388,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -398,7 +399,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); } break; case FormatDescriptor.Type.Fill: @@ -515,7 +516,7 @@ namespace SourceGen.AsmGen { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); OutputEquDirective(SourceFormatter.FormatVariableLabel(defSym.Label), valueStr, defSym.Comment); } diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 35cdfce..43b6d8a 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -189,6 +189,7 @@ namespace SourceGen.AsmGen { config.mEndOfLineCommentDelimiter = ";"; config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; + config.mNonUniqueLabelPrefix = "@"; config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Cc65; Formatter.DelimiterSet charSet = new Formatter.DelimiterSet(); @@ -421,7 +422,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -432,7 +433,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); } break; case FormatDescriptor.Type.Fill: @@ -547,7 +548,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.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); 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 6faae0e..f6e3223 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -160,10 +160,11 @@ namespace SourceGen.AsmGen { config.mForceDirectOperandPrefix = string.Empty; config.mForceAbsOperandPrefix = string.Empty; config.mForceLongOperandPrefix = string.Empty; - config.mLocalVariableLablePrefix = "]"; + config.mLocalVariableLabelPrefix = "]"; config.mEndOfLineCommentDelimiter = ";"; config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = string.Empty; + config.mNonUniqueLabelPrefix = ":"; config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Merlin; Formatter.DelimiterSet charSet = new Formatter.DelimiterSet(); @@ -248,7 +249,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -259,7 +260,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); } break; case FormatDescriptor.Type.Fill: @@ -439,7 +440,7 @@ namespace SourceGen.AsmGen { foreach (DefSymbol defSym in newDefs) { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); 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 78bf284..767c223 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -197,6 +197,7 @@ namespace SourceGen.AsmGen { config.mEndOfLineCommentDelimiter = ";"; config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; + config.mNonUniqueLabelPrefix = ""; // should be '_', but that's a valid label char config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Common; } @@ -453,7 +454,7 @@ namespace SourceGen.AsmGen { UpdateCharacterEncoding(dfd); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); @@ -465,7 +466,7 @@ namespace SourceGen.AsmGen { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); } break; case FormatDescriptor.Type.Fill: @@ -582,7 +583,7 @@ namespace SourceGen.AsmGen { foreach (DefSymbol defSym in newDefs) { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); 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 48c3db3..ef6f522 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.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); 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.StripAnnotation; + PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix; bool isPcRelBankWrap = false; // Tweak branch instructions. We want to show the absolute address rather @@ -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.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand & 0xff, 1, - PseudoOp.FormatNumericOpFlags.StripAnnotation); + PseudoOp.FormatNumericOpFlags.StripLabelPrefixSuffix); if (gen.Quirks.BlockMoveArgsReversed) { string tmp = opstr1; opstr1 = opstr2; diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 08f73cc..7c8fbdf 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -1098,6 +1098,10 @@ namespace SourceGen { /// Merges symbols from UserLabels into SymbolTable. Existing entries with matching /// labels will be replaced. /// + /// + /// It might make sense to exclude non-unique labels, but that's probably better done + /// with a UI filter option. + /// 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, which can change if diff --git a/SourceGen/DisplayList.cs b/SourceGen/DisplayList.cs index f8941af..14bb69f 100644 --- a/SourceGen/DisplayList.cs +++ b/SourceGen/DisplayList.cs @@ -322,6 +322,9 @@ namespace SourceGen { OnPropertyChanged(CountString); OnPropertyChanged(IndexerName); + // TODO(performance): this causes the ListView to format the entire listing, despite + // being virtual. So we're regenerating the entire list after something trivial, + // like renaming a label. Need to figure this out. OnCollectionReset(); } diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index 666b007..4b604fc 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -871,7 +871,8 @@ namespace SourceGen { PseudoOp.FormatNumericOpFlags.None); valueStr = PseudoOp.AnnotateEquDirective(formatter, valueStr, defSym); string comment = formatter.FormatEolComment(defSym.Comment); - FormattedParts parts = FormattedParts.CreateEquDirective(defSym.AnnotatedLabel, + FormattedParts parts = FormattedParts.CreateEquDirective( + defSym.GenerateDisplayLabel(formatter), formatter.FormatPseudoOp(opNames.EquDirective), valueStr, comment); line.Parts = parts; @@ -1183,7 +1184,7 @@ namespace SourceGen { string labelStr = string.Empty; if (attr.Symbol != null) { - labelStr = attr.Symbol.AnnotatedLabel; + labelStr = attr.Symbol.GenerateDisplayLabel(mFormatter); } OpDef op = mProject.CpuDef.GetOpDef(data[offset]); @@ -1316,7 +1317,7 @@ namespace SourceGen { addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16); if (attr.Symbol != null) { - labelStr = attr.Symbol.AnnotatedLabel; + labelStr = attr.Symbol.GenerateDisplayLabel(mFormatter); } bytesStr = mFormatter.FormatBytes(data, offset, attr.Length); @@ -1394,7 +1395,7 @@ namespace SourceGen { addrStr = PseudoOp.AnnotateEquDirective(mFormatter, addrStr, defSym); string comment = mFormatter.FormatEolComment(defSym.Comment); return FormattedParts.CreateEquDirective( - mFormatter.FormatVariableLabel(defSym.AnnotatedLabel), + mFormatter.FormatVariableLabel(defSym.GenerateDisplayLabel(mFormatter)), mFormatter.FormatPseudoOp(mPseudoOpNames.VarDirective), addrStr, comment); } @@ -1436,7 +1437,7 @@ namespace SourceGen { addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16); if (attr.Symbol != null) { - labelStr = attr.Symbol.AnnotatedLabel; + labelStr = attr.Symbol.GenerateDisplayLabel(mFormatter); } else { labelStr = string.Empty; } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index bb577dd..4f6ad03 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -466,7 +466,9 @@ namespace SourceGen { mFormatterConfig.mFullLineCommentDelimiterBase = ";"; mFormatterConfig.mBoxLineCommentDelimiter = string.Empty; - mFormatterConfig.mLocalVariableLablePrefix = + mFormatterConfig.mNonUniqueLabelPrefix = + settings.GetString(AppSettings.FMT_NON_UNIQUE_LABEL_PREFIX, string.Empty); + mFormatterConfig.mLocalVariableLabelPrefix = settings.GetString(AppSettings.FMT_LOCAL_VARIABLE_PREFIX, string.Empty); string chrDelCereal = settings.GetString(AppSettings.FMT_CHAR_DELIM, null); @@ -1709,8 +1711,8 @@ namespace SourceGen { int offset = CodeLineList[selIndex].FileOffset; Anattrib attr = mProject.GetAnattrib(offset); - EditLabel dlg = new EditLabel(mMainWin, attr.Symbol, attr.Address, - mProject.SymbolTable); + EditLabel dlg = new EditLabel(mMainWin, attr.Symbol, attr.Address, offset, + mProject.SymbolTable, mOutputFormatter); if (dlg.ShowDialog() != true) { return; } @@ -3456,6 +3458,10 @@ namespace SourceGen { #region Symbols panel + /// + /// Populates the ItemsSource for the Symbols window. Each entry in the project + /// symbol table is added. + /// private void PopulateSymbolsList() { mMainWin.SymbolsList.Clear(); foreach (Symbol sym in mProject.SymbolTable) { @@ -3475,7 +3481,7 @@ namespace SourceGen { } MainWindow.SymbolsListItem sli = new MainWindow.SymbolsListItem(sym, - sourceTypeStr, valueStr, sym.AnnotatedLabel); + sourceTypeStr, valueStr, sym.GenerateDisplayLabel(mOutputFormatter)); mMainWin.SymbolsList.Add(sli); } } diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index eb4564e..4d8a0c7 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -549,10 +549,10 @@ namespace SourceGen { /// [Flags] public enum FormatNumericOpFlags { - 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 + None = 0, + IsPcRel = 1, // opcode is PC relative, e.g. branch or PER + HasHashPrefix = 1 << 1, // operand has a leading '#', reducing ambiguity + StripLabelPrefixSuffix = 1 << 2, // don't show annotation char or non-unique prefix } /// @@ -641,7 +641,7 @@ 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 + // For local variables we're doing a trivial add and don't // wrap, so the "common" format works for everybody. StringBuilder sb = new StringBuilder(); FormatNumericSymbolCommon(formatter, defSym, null, @@ -699,6 +699,8 @@ namespace SourceGen { int adjustment, symbolValue; + // Start by remapping the label, if necessary. The remapped label may have a + // local-variable prefix character. string symLabel = sym.Label; if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; @@ -706,9 +708,16 @@ 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); + + // Now put the prefix/suffix back on if desired. We don't want to mess with it + // if it's from the assembler. + if ((flags & FormatNumericOpFlags.StripLabelPrefixSuffix) == 0) { + symLabel = Symbol.ConvertLabelForDisplay(symLabel, sym.LabelAnno, + sym.IsNonUnique, formatter); + } else { + // TODO(xyzzy): remapper will handle this + symLabel = Symbol.ConvertLabelForDisplay(symLabel, Symbol.LabelAnnotation.None, + false, formatter); } if (operandLen == 1) { @@ -845,9 +854,13 @@ 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 ((flags & FormatNumericOpFlags.StripLabelPrefixSuffix) == 0) { + symLabel = Symbol.ConvertLabelForDisplay(symLabel, sym.LabelAnno, + sym.IsNonUnique, formatter); + } else { + // TODO(xyzzy): remapper will handle this + symLabel = Symbol.ConvertLabelForDisplay(symLabel, Symbol.LabelAnnotation.None, + false, formatter); } if (operandLen == 1) { @@ -942,9 +955,13 @@ 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 ((flags & FormatNumericOpFlags.StripLabelPrefixSuffix) == 0) { + symLabel = Symbol.ConvertLabelForDisplay(symLabel, sym.LabelAnno, + sym.IsNonUnique, formatter); + } else { + // TODO(xyzzy): remapper will handle this + symLabel = Symbol.ConvertLabelForDisplay(symLabel, Symbol.LabelAnnotation.None, + false, formatter); } int adjustment; diff --git a/SourceGen/Sandbox/ScriptManager.cs b/SourceGen/Sandbox/ScriptManager.cs index cc4b848..9970a32 100644 --- a/SourceGen/Sandbox/ScriptManager.cs +++ b/SourceGen/Sandbox/ScriptManager.cs @@ -224,6 +224,9 @@ namespace SourceGen.Sandbox { } PlSymbol.Type plsType; switch (sym.SymbolType) { + case Symbol.Type.NonUniqueLocalAddr: + // don't forward these to plugins + continue; case Symbol.Type.LocalOrGlobalAddr: case Symbol.Type.GlobalAddr: case Symbol.Type.GlobalAddrExport: diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index cc4c857..af15c1a 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -15,6 +15,7 @@ */ using System; using System.Diagnostics; +using System.Text; namespace SourceGen { /// @@ -22,6 +23,9 @@ namespace SourceGen { /// public class Symbol { public const char UNCERTAIN_CHAR = '?'; + private const char NO_ANNO_CHAR = '\ufffd'; // REPLACEMENT CHARACTER '�' + private const char UNIQUE_TAG_CHAR = '\u00a7'; // SECTION SIGN + private const int NON_UNIQUE_LEN = 7; // NON_UNIQUE_CHAR + 6 hex digits /// /// How was the symbol defined? @@ -42,6 +46,16 @@ namespace SourceGen { /// Unique or non-unique address label? Is it required to be global or exported? /// Constants get a separate type. /// + /// + /// There's really just three types: unique address symbol, non-unique address symbol, + /// and constant. There's also a set of boolean flags indicating whether the symbol + /// should be forced to be global, whether it should be included in the export table, + /// and whether it's internal or external. + /// + /// It turns out that many combinations of type and flag don't actually make sense, + /// e.g. I don't know what a non-unique exported external constant is, so we just + /// enumerate the combinations that make sense. + /// public enum Type { Unknown = 0, @@ -103,11 +117,6 @@ namespace SourceGen { /// 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 @@ -124,6 +133,13 @@ namespace SourceGen { get { return SymbolSource == Source.Variable; } } + /// + /// True if the symbol is a non-unique local. + /// + public bool IsNonUnique { + get { return SymbolType == Type.NonUniqueLocalAddr; } + } + /// /// True if the symbol represents a constant value. /// @@ -142,10 +158,11 @@ namespace SourceGen { /// Symbol value. /// User-defined, auto-generated, ? /// Type of symbol this is. + /// Optional annotation. public Symbol(string label, int value, Source source, Type type, LabelAnnotation labelAnno) { Debug.Assert(Asm65.Label.ValidateLabel(label)); - Debug.Assert(type != Type.NonUniqueLocalAddr); + Debug.Assert(type != Type.NonUniqueLocalAddr || value == 0xdead); // use other ctor Label = label; Value = value; SymbolType = type; @@ -172,24 +189,80 @@ namespace SourceGen { 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) + /// Label string. Syntax assumed valid. + /// Symbol value. + /// Optional annotation. + /// Tag that makes a non-unique label unique, e.g. the + /// offset for which a user label has been created. + public Symbol(string label, int value, LabelAnnotation labelAnno, int uniqueTag) + : this(label, 0xdead, Source.User, Type.NonUniqueLocalAddr, labelAnno) { + Debug.Assert(uniqueTag >= 0 && uniqueTag < 0x01000000); // fit in 6 hex digits + Debug.Assert(label.IndexOf(UNIQUE_TAG_CHAR) < 0); // already extended? + + Value = value; // passed a bogus value earlier for assert + + // Add tag to label to make it unique. + Label = label + UNIQUE_TAG_CHAR + uniqueTag.ToString("x6"); + } + + /// + /// Generates a displayable form of the label. This will have the non-unique label + /// prefix and annotation suffix, and will have the non-unique tag removed. + /// + /// Formatter object. + /// Label suitable for display. + public string GenerateDisplayLabel(Asm65.Formatter formatter) { + return ConvertLabelForDisplay(Label, LabelAnno, IsNonUnique, formatter); + } + + /// + /// Returns the annotation suffix character, or NO_ANNO_CHAR if nothing appropriate. + /// + private static char GetLabelAnnoChar(LabelAnnotation anno) { + char ch = NO_ANNO_CHAR; + if (anno == LabelAnnotation.Uncertain) { + ch = UNCERTAIN_CHAR; + } else if (anno == LabelAnnotation.Generated) { + //ch = '\u00a4'; // CURRENCY SIGN '¤' + } + return ch; + } + + /// + /// 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. + /// + /// Base label string. Has the uniquification tag, but no + /// annotation char or non-unique prefix. + /// Annotation; may be None. + /// Formatted label. + public static string ConvertLabelForDisplay(string label, LabelAnnotation anno, + bool isNonUnique, Asm65.Formatter formatter) { + StringBuilder sb = new StringBuilder(label.Length + 2); + + if (isNonUnique) { + 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)); + } else { + sb.Append(label); + } + + char annoChar = GetLabelAnnoChar(anno); + if (annoChar != NO_ANNO_CHAR) { + sb.Append(annoChar); + } + return sb.ToString(); } /// @@ -199,14 +272,20 @@ namespace SourceGen { /// trimmed version of the string is returned. /// /// Label to examine. + /// For address symbols, the prefix string for + /// non-unique labels. 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. + /// True if the first character indicates that this is + /// a non-unique label. /// 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) { + public static string TrimAndValidateLabel(string label, string nonUniquePrefix, + out bool isValid, out bool isLenValid, out bool isFirstCharValid, + out bool hasNonUniquePrefix, out LabelAnnotation anno) { anno = LabelAnnotation.None; + hasNonUniquePrefix = false; // Do we have at least one char? if (string.IsNullOrEmpty(label)) { @@ -221,6 +300,14 @@ namespace SourceGen { trimLabel = trimLabel.Substring(0, trimLabel.Length - 1); } + // Check for leading non-unique ident char. + if (trimLabel.Length > 0 && !string.IsNullOrEmpty(nonUniquePrefix)) { + if (trimLabel[0] == nonUniquePrefix[0]) { + hasNonUniquePrefix = true; + trimLabel = trimLabel.Substring(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, @@ -229,22 +316,6 @@ namespace SourceGen { 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; - } - } - public override string ToString() { return Label + "{" + SymbolSource + "," + SymbolType + diff --git a/SourceGen/WpfGui/EditAppSettings.xaml b/SourceGen/WpfGui/EditAppSettings.xaml index d09c3de..cadf92f 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml +++ b/SourceGen/WpfGui/EditAppSettings.xaml @@ -504,7 +504,8 @@ limitations under the License. + IsChecked="{Binding DisableLabelLocalization}" + Visibility="Collapsed"/> @@ -571,6 +572,14 @@ limitations under the License. + + + + + + + + _Non-unique local ('{0}') + + @@ -47,18 +53,24 @@ limitations under the License. - + - - - + + + + - - private SymbolTable mSymbolTable; + /// + /// Label formatter. + /// + private Formatter mFormatter; + // Dialog label text color, saved off at dialog load time. private Brush mDefaultLabelColor; + /// + /// Recursion guard. + /// + private bool mInUpdateControls; + + public string NonUniqueButtonLabel { get; private set; } + /// /// Set to true when input is valid. Controls whether the OK button is enabled. /// public bool IsValid { get { return mIsValid; } - set { - mIsValid = value; - OnPropertyChanged(); - } + set { mIsValid = value; OnPropertyChanged(); } } private bool mIsValid; @@ -61,14 +77,51 @@ namespace SourceGen.WpfGui { /// public string LabelText { get { return mLabelText; } - set { - mLabelText = value; - LabelTextBox_TextChanged(); - OnPropertyChanged(); - } + set { mLabelText = value; OnPropertyChanged(); UpdateControls(); } } string mLabelText; + // Radio buttons. + public bool mIsNonUniqueChecked, mIsNonUniqueEnabled; + public bool IsNonUniqueChecked { + get { return mIsNonUniqueChecked; } + set { mIsNonUniqueChecked = value; OnPropertyChanged(); UpdateControls(); } + } + public bool IsNonUniqueEnabled { + get { return mIsNonUniqueEnabled; } + set { mIsNonUniqueEnabled = value; OnPropertyChanged(); } + } + + public bool mIsLocalChecked, mIsLocalEnabled; + public bool IsLocalChecked { + get { return mIsLocalChecked; } + set { mIsLocalChecked = value; OnPropertyChanged(); UpdateControls(); } + } + public bool IsLocalEnabled { + get { return mIsLocalEnabled; } + set { mIsLocalEnabled = value; OnPropertyChanged(); } + } + + public bool mIsGlobalChecked, mIsGlobalEnabled; + public bool IsGlobalChecked { + get { return mIsGlobalChecked; } + set { mIsGlobalChecked = value; OnPropertyChanged(); UpdateControls(); } + } + public bool IsGlobalEnabled { + get { return mIsGlobalEnabled; } + set { mIsGlobalEnabled = value; OnPropertyChanged(); } + } + + public bool mIsExportedChecked, mIsExportedEnabled; + public bool IsExportedChecked { + get { return mIsExportedChecked; } + set { mIsExportedChecked = value; OnPropertyChanged(); UpdateControls(); } + } + public bool IsExportedEnabled { + get { return mIsExportedEnabled; } + set { mIsExportedEnabled = value; OnPropertyChanged(); } + } + // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = "") { @@ -76,43 +129,53 @@ namespace SourceGen.WpfGui { } - public EditLabel(Window owner, Symbol origSym, int address, SymbolTable symbolTable) { + public EditLabel(Window owner, Symbol origSym, int address, int uniqueTag, + SymbolTable symbolTable, Formatter formatter) { InitializeComponent(); Owner = owner; DataContext = this; LabelSym = origSym; mAddress = address; + mUniqueTag = uniqueTag; mSymbolTable = symbolTable; + mFormatter = formatter; + + string fmt = (string)FindResource("str_NonUniqueLocalFmt"); + NonUniqueButtonLabel = string.Format(fmt, mFormatter.NonUniqueLabelPrefix); } private void Window_Loaded(object sender, RoutedEventArgs e) { mDefaultLabelColor = maxLengthLabel.Foreground; + IsNonUniqueEnabled = IsLocalEnabled = IsGlobalEnabled = IsExportedEnabled = true; + if (LabelSym == null) { LabelText = string.Empty; - radioButtonLocal.IsChecked = true; + IsGlobalChecked = true; } else { - LabelText = LabelSym.AnnotatedLabel; + LabelText = LabelSym.GenerateDisplayLabel(mFormatter); switch (LabelSym.SymbolType) { case Symbol.Type.NonUniqueLocalAddr: - Debug.Assert(false); // TODO(xyzzy) + IsNonUniqueChecked = true; break; case Symbol.Type.LocalOrGlobalAddr: - radioButtonLocal.IsChecked = true; + IsLocalChecked = true; break; case Symbol.Type.GlobalAddr: - radioButtonGlobal.IsChecked = true; + IsGlobalChecked = true; break; case Symbol.Type.GlobalAddrExport: - radioButtonExport.IsChecked = true; + IsExportedChecked = true; break; default: Debug.Assert(false); // WTF - radioButtonLocal.IsChecked = true; + IsGlobalChecked = true; break; } } + + UpdateControls(); } private void Window_ContentRendered(object sender, EventArgs e) { @@ -120,48 +183,39 @@ namespace SourceGen.WpfGui { labelTextBox.Focus(); } - private void LabelTextBox_TextChanged() { -#if false - string str = LabelText; - bool valid = true; - - if (str.Length == 1 || str.Length > Asm65.Label.MAX_LABEL_LEN) { - valid = false; - maxLengthLabel.Foreground = Brushes.Red; - } else { - maxLengthLabel.Foreground = mDefaultLabelColor; + private void UpdateControls() { + if (mInUpdateControls) { + return; } + mInUpdateControls = true; - // Regex never matches on strings of length 0 or 1, but we don't want - // to complain about that since we're already doing that above. - // TODO(maybe): Ideally this wouldn't light up if the only problem was a - // non-alpha first character, since the next test will call that out. - if (str.Length > 1) { - if (!Asm65.Label.ValidateLabel(str)) { - valid = false; - validCharsLabel.Foreground = Brushes.Red; - } else { - validCharsLabel.Foreground = mDefaultLabelColor; - } - } else { - validCharsLabel.Foreground = mDefaultLabelColor; - } + LabelTextChanged(); - if (str.Length > 0 && - !((str[0] >= 'A' && str[0] <= 'Z') || (str[0] >= 'a' && str[0] <= 'z') || - str[0] == '_')) { - // This should have been caught by the regex. We just want to set the - // color on the "first character must be" instruction text. - Debug.Assert(!valid); - firstLetterLabel.Foreground = Brushes.Red; - } else { - firstLetterLabel.Foreground = mDefaultLabelColor; - } -#endif + mInUpdateControls = false; + } + + private bool mHadNonUniquePrefix = false; + + private void LabelTextChanged() { bool isBlank = (LabelText.Length == 0); - string trimLabel = Symbol.TrimAndValidateLabel(LabelText, out bool isValid, - out bool isLenValid, out bool isFirstCharValid, out Symbol.LabelAnnotation anno); + // Strip leading non-unique prefix and the trailing annotation. + string trimLabel = Symbol.TrimAndValidateLabel(LabelText, + mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool isLenValid, + out bool isFirstCharValid, out bool hasNonUniquePrefix, + out Symbol.LabelAnnotation anno); + + // If they type '@'/':'/'.' at the start of the label, switch the radio button. + // Alternatively, if they choose a different radio button, remove the prefix. + // We only want to do this on the first event so we don't wedge the control. + if (hasNonUniquePrefix && !mHadNonUniquePrefix && !IsNonUniqueChecked) { + IsNonUniqueChecked = true; + } else if (hasNonUniquePrefix && mHadNonUniquePrefix && !IsNonUniqueChecked) { + LabelText = LabelText.Substring(1); + hasNonUniquePrefix = false; + } + mHadNonUniquePrefix = hasNonUniquePrefix; + if (isBlank || isLenValid) { maxLengthLabel.Foreground = mDefaultLabelColor; } else { @@ -180,18 +234,30 @@ namespace SourceGen.WpfGui { 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 - // auto label, we want to force them to change the name.) +#if false + if (hasNonUniqueTag) { + IsNonUniqueChecked = true; + IsLocalEnabled = IsGlobalEnabled = IsExportedEnabled = false; + } else { + IsNonUniqueEnabled = IsLocalEnabled = IsGlobalEnabled = IsExportedEnabled = true; + } +#endif + + // Refuse to continue if the label already exists and this isn't a non-unique label. + // The only exception is if it's the same symbol, and it's user-defined. (If + // they're trying to edit an auto label, we want to force them to change the name.) // // NOTE: if label matching is case-insensitive, we want to allow a situation // 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 (isValid && mSymbolTable.TryGetValue(trimLabel, out Symbol sym) && + if (!IsNonUniqueChecked && isValid && + mSymbolTable.TryGetValue(trimLabel, out Symbol sym) && (sym != LabelSym || LabelSym.SymbolSource != Symbol.Source.User)) { isValid = false; notDuplicateLabel.Foreground = Brushes.Red; + } else if (IsNonUniqueChecked) { + notDuplicateLabel.Foreground = Brushes.Gray; } else { notDuplicateLabel.Foreground = mDefaultLabelColor; } @@ -204,23 +270,32 @@ namespace SourceGen.WpfGui { LabelSym = null; } else { Symbol.Type symbolType; - // TODO(xyzzy): non-local - if (radioButtonLocal.IsChecked == true) { + if (IsNonUniqueChecked) { + symbolType = Symbol.Type.NonUniqueLocalAddr; + } else if (IsLocalChecked == true) { symbolType = Symbol.Type.LocalOrGlobalAddr; - } else if (radioButtonGlobal.IsChecked == true) { + } else if (IsGlobalChecked == true) { symbolType = Symbol.Type.GlobalAddr; - } else if (radioButtonExport.IsChecked == true) { + } else if (IsExportedChecked == true) { symbolType = Symbol.Type.GlobalAddrExport; } else { Debug.Assert(false); // WTF - symbolType = Symbol.Type.LocalOrGlobalAddr; + symbolType = Symbol.Type.GlobalAddr; } - // Parse and strip the annotation. - string trimLabel = Symbol.TrimAndValidateLabel(LabelText, out bool unused1, - out bool unused2, out bool unused3, out Symbol.LabelAnnotation anno); + // Parse and strip the annotation and optional non-unique tag. + string trimLabel = Symbol.TrimAndValidateLabel(LabelText, + mFormatter.NonUniqueLabelPrefix, out bool unused1, out bool unused2, + out bool unused3, out bool hasNonUniquePrefix, + out Symbol.LabelAnnotation anno); - LabelSym = new Symbol(trimLabel, mAddress, Symbol.Source.User, symbolType, anno); + if (IsNonUniqueChecked) { + LabelSym = new Symbol(trimLabel, mAddress, anno, mUniqueTag); + } else { + Debug.Assert(!hasNonUniquePrefix); + 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 1f55e79..a94ddcf 100644 --- a/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs +++ b/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs @@ -175,7 +175,7 @@ namespace SourceGen.WpfGui { FormattedSymbol fsym = new FormattedSymbol( defSym, - defSym.AnnotatedLabel, + defSym.GenerateDisplayLabel(mFormatter), mFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase), typeStr, defSym.DataDescriptor.Length, diff --git a/SourceGen/WpfGui/EditProjectProperties.xaml.cs b/SourceGen/WpfGui/EditProjectProperties.xaml.cs index d7056c7..0b1f6a9 100644 --- a/SourceGen/WpfGui/EditProjectProperties.xaml.cs +++ b/SourceGen/WpfGui/EditProjectProperties.xaml.cs @@ -485,7 +485,7 @@ namespace SourceGen.WpfGui { FormattedSymbol fsym = new FormattedSymbol( defSym, - defSym.AnnotatedLabel, + defSym.GenerateDisplayLabel(mFormatter), mFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase), typeStr, defSym.HasWidth ? defSym.DataDescriptor.Length.ToString() : NO_WIDTH_STR,