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.
This commit is contained in:
Andy McFadden 2019-11-08 20:44:45 -08:00
parent b4519b2aa2
commit 4d079c8d14
26 changed files with 469 additions and 238 deletions

View File

@ -613,7 +613,8 @@ namespace Asm65 {
}
/// <summary>
/// Formats a local variable label, prepending a prefix if needed.
/// Formats a local variable label, prepending an identifying prefix if one has been
/// specified.
/// </summary>
public string FormatVariableLabel(string label) {
if (!string.IsNullOrEmpty(mFormatConfig.mLocalVariableLablePrefix)) {

View File

@ -59,13 +59,40 @@ namespace Asm65 {
/// <param name="label">Label to validate.</param>
/// <returns>True if the label is correctly formed.</returns>
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;
}
/// <summary>
/// Performs a detailed validation of a symbol label, breaking out different failure
/// causes for the benefit of code that reports errors to the user.
/// </summary>
/// <param name="label">Label to examine.</param>
/// <param name="isLenValid">True if the label has a valid length.</param>
/// <param name="isFirstCharValid">True if the first character is valid.</param>
/// <returns>True if the label is valid.</returns>
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;
}
/// <summary>
/// Returns "normal form" of label. This matches LABEL_COMPARER behavior.
/// </summary>

View File

@ -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);
}

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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;

View File

@ -81,12 +81,13 @@ namespace SourceGen.AsmGen {
/// <summary>
/// A pairing of an offset with a label string. (Essentially mAnattribs[n].Symbol
/// with all the fluff trimmed away.)
///
/// </summary>
/// <remarks>
/// 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.
/// </summary>
/// </remarks>
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<string, string>();
}
// 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<string, string> allLabels = new SortedList<string, string>();
for (int i = 0; i < mProject.FileDataLength; i++) {

View File

@ -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;
}

View File

@ -170,8 +170,9 @@ namespace SourceGen {
/// <summary>
/// Internal base-object (Symbol) constructor, called by other constructors.
/// </summary>
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.</param>
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) { }
/// <summary>
@ -211,9 +212,10 @@ namespace SourceGen {
/// <param name="tag">Symbol tag, used for grouping platform symbols.</param>
/// false, the value of the "width" argument is ignored.</param>
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);
}
/// <summary>
@ -296,8 +299,9 @@ namespace SourceGen {
/// <param name="label">Label to use.</param>
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)
{ }
/// <summary>

View File

@ -1100,10 +1100,10 @@ namespace SourceGen {
/// </summary>
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<int, Symbol> changes = new Dictionary<int, Symbol>();
@ -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;
}

View File

@ -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;
}

View File

@ -22,6 +22,14 @@ namespace SourceGen {
/// Given a list of LocalVariableTables, this determines the mapping of values to symbols
/// at a specific offset.
/// </summary>
/// <remarks>
/// 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]
/// </remarks>
public class LocalVariableLookup {
/// <summary>
/// List of tables. The table's file offset is used as the key.

View File

@ -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);
}
}

View File

@ -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 {
}
/// <summary>
/// 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.
/// </summary>
/// <param name="ssym">Deserialized data.</param>
/// <param name="report">Error report object.</param>
@ -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;
}

View File

@ -547,10 +547,12 @@ namespace SourceGen {
/// <summary>
/// Special formatting flags for the FormatNumericOperand() method.
/// </summary>
[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
}
/// <summary>
@ -595,7 +597,8 @@ namespace SourceGen {
}
/// <summary>
/// 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.
/// </summary>
/// <param name="formatter">Text formatter.</param>
/// <param name="symbolTable">Full table of project symbols.</param>
@ -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;

View File

@ -35,7 +35,8 @@ and 65816 code. The official web site is
<li><a href="intro.html#sgconcepts">SourceGen Concepts</a></li>
<li><a href="intro.html#about-symbols">All About Symbols</a>
<ul>
<li><a href="intro.html#local-vars">Local Variables</a></li>
<li><a href="intro.html#internal-address-symbols">Internal Address Symbols</a></li>
<li><a href="intro.html#external-address-symbols">External Address Symbols</a></li>
<li><a href="intro.html#weak-refs">Weak References</a></li>
<li><a href="intro.html#symbol-parts">Parts and Adjustments</a></li>
<li><a href="intro.html#nearby-targets">Automatic Use of Nearby Targets</a></li>

View File

@ -66,7 +66,7 @@ rest of the documentation assumes you've read and understood this.</p>
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.</p>
general programming concepts to make sense of this, however.</p>
<h2><a name="begin">About 6502 Code</a></h2>
@ -407,7 +407,7 @@ by splitting formatted data into sub-regions at label boundaries.</p>
<h2><a name="about-symbols">All About Symbols</a></h2>
<p>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.</p>
@ -420,6 +420,11 @@ with most assemblers:</p>
</ul>
<p>Label comparisons are case-sensitive, as is customary for programming
languages.</p>
<p>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.</p>
<p>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. <code>__label</code>) for its own
@ -437,7 +442,46 @@ MYSTRING .STR "hello"
</pre>
<p>See <a href="#symbol-parts">Parts and Adjustments</a> for more details.</p>
<h4><a name="symbol-types">Symbol Types</a></h4>
<p>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.</p>
<h3><a name="internal-address-symbols">Internal Address Symbols</a></h3>
<p>Symbols that represent an address inside the file being disassembled
are referred to as <i>internal</i>. They come in two varieties.</p>
<p><b>User labels</b> 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.</p>
<p><b>Auto labels</b> 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.</p>
<h3><a name="external-address-symbols">External Address Symbols</a></h3>
<p>Symbols that represent an address outside the file being disassembled
are referred to as <i>external</i>. 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.</p>
<p><b>Platform symbols</b> 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 <code>RuntimeData</code> directory. You can also create your
own, but they have to live in the same directory as the project file.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><b>Project symbols</b> 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.</p>
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.</p>
<p>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.)</p>
<p><b>Local variables</b> are redefinable symbols that are organized
into tables. They're used to specify labels for zero-page and 65816
stack-relative instructions.</p>
<p><b>User labels</b> 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.</p>
<p><b>Auto labels</b> 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.</p>
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.</p>
<h3><a name="local-vars">Local Variables</a></h3>
<h4><a name="local-vars">How Local Variables Work</a></h4>
<p>Local variables are applied to instructions that have zero
page operands (<code>op ZP</code>, <code>op (ZP),Y</code>, etc.), or
@ -524,7 +549,7 @@ example:</p>
<pre>
LDA ($00),Y
INC $02
... later ...
... elsewhere ...
DEC $00
STA ($01),Y
</pre>

View File

@ -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.</p>
<p>Select the line at address $2063, and use Actions &gt; 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 <code>LDA #&lt;XDATA</code>
and <code>LDA #&gt;XDATA</code>.</p>
went well, the operands should now read <code>LDA #&lt;XDATA?</code>
and <code>LDA #&gt;XDATA?</code>.</p>
<p>Let's give the pointer a name. Select line $203d, and use
Actions &gt; Create Local Variable Table to create an empty table.
Click "New Symbol" on the right side. Leave the Address button selected.

View File

@ -21,6 +21,8 @@ namespace SourceGen {
/// Symbolic representation of a value. Instances are immutable.
/// </summary>
public class Symbol {
public const char UNCERTAIN_CHAR = '?';
/// <summary>
/// How was the symbol defined?
/// </summary>
@ -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
}
/// <summary>
/// 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.
/// </summary>
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
}
/// <summary>
/// User-specified commentary on the label.
/// </summary>
public enum LabelAnnotation {
None = 0,
Uncertain, // user isn't sure if this is correct
Generated // label was generated, e.g. address table formatter
}
/// <summary>
/// Unique label.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public string Label { get; private set; }
/// <summary>
/// Symbol's 32-bit numeric value.
/// </summary>
/// <remarks>
/// For address types, the value should be constained to [0,2^24). For constants,
/// all values are valid.
/// </remarks>
public int Value { get; private set; }
/// <summary>
/// Symbol origin, e.g. auto-generated or entered by user. Enum values are in
/// priority order.
/// </summary>
public Source SymbolSource { get; private set; }
/// <summary>
/// Type of symbol, e.g. local or global.
/// </summary>
public Type SymbolType { get; private set; }
/// <summary>
/// Notes on the label.
/// </summary>
public LabelAnnotation LabelAnno { get; private set; }
/// <summary>
/// Two-character string representation of Source and Type, for display in the UI.
/// Generated from SymbolSource and SymbolType.
/// </summary>
public string SourceTypeString { get; private set; }
/// <summary>
/// Label with annotations. Generated from Label and LabelAnno.
/// </summary>
public string AnnotatedLabel { get; private set; }
/// <summary>
/// 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; }
}
/// <summary>
/// True if the symbol represents a constant value.
/// </summary>
public bool IsConstant {
get { return SymbolType == Type.Constant; }
}
/// <summary>
/// Label sent to assembler.
/// </summary>
public string Label { get; private set; }
/// <summary>
/// Symbol's numeric value.
/// </summary>
public int Value { get; private set; }
/// <summary>
/// Symbol origin, e.g. auto-generated or entered by user. Enum values are in
/// priority order.
/// </summary>
public Source SymbolSource { get; private set; }
/// <summary>
/// Type of symbol, e.g. local or global.
/// </summary>
public Type SymbolType { get; private set; }
/// <summary>
/// Two-character string representation of Source and Type, for display in the UI.
/// </summary>
public string SourceTypeString { get; private set; }
// No nullary constructor.
private Symbol() { }
/// <summary>
/// Constructs immutable object.
/// Basic constructor.
/// </summary>
/// <param name="label">Label string. Syntax assumed valid.</param>
/// <param name="value">Symbol value.</param>
/// <param name="source">User-defined, auto-generated, ?</param>
/// <param name="type">Type of symbol this is.</param>
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);
}
/// <summary>
/// Constructor for non-unique labels.
/// </summary>
/// <param name="label"></param>
/// <param name="value"></param>
/// <param name="source"></param>
/// <param name="type"></param>
/// <param name="labelAnno"></param>
/// <param name="offset"></param>
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)
}
/// <summary>
/// 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.
/// </summary>
/// <param name="label">Label to examine.</param>
/// <param name="isValid">True if the entire label is valid.</param>
/// <param name="isLenValid">True if the label has a valid length.</param>
/// <param name="isFirstCharValid">True if the first character is valid.</param>
/// <param name="anno">Annotation found, or None if none found.</param>
/// <returns>Trimmed version of the string.</returns>
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;
}
/// <summary>
/// Augments a label string with an annotation identifier.
/// </summary>
/// <param name="label">String to augment.</param>
/// <param name="anno">Annotation; may be None.</param>
/// <returns>Original or updated string.</returns>
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;
}

View File

@ -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;

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<FormattedSymbol> 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;
}

View File

@ -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);