1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-05-31 22:41:37 +00:00

External symbol I/O direction and address mask, part 1

Memory-mapped I/O locations can have different behavior when read
vs. written.  This is part 1 of a change to allow two different
symbols to represent the same address, based on I/O direction.

This also adds a set of address masks for systems like the Atari
2600 that map hardware addresses to multiple locations.

This change updates the data structures, .sym65 file reader,
project serialization, and DefSymbol editor.
This commit is contained in:
Andy McFadden 2019-10-15 16:37:14 -07:00
parent fac2d6a51f
commit 9c3422623d
14 changed files with 579 additions and 194 deletions

View File

@ -174,6 +174,11 @@ namespace Asm65 {
/// <summary>
/// Effect this instruction has on memory.
/// </summary>
/// <remarks>
/// We don't consider execution to have a memory effect, so "LDA $1000" is Read but
/// "JMP $1000" is None. That's because the instruction itself doesn't access the
/// memory at $1000, it just changes the program counter to point there.
/// </remarks>
public MemoryEffect MemEffect {
get {
// If we do this a lot, we should probably just go through and set the

View File

@ -80,6 +80,62 @@ namespace SourceGen {
/// </summary>
public string FileIdentifier { get; private set; }
/// <summary>
/// I/O direction enumeration.
/// </summary>
/// <remarks>
/// The numeric value determines the sort order in the Symbols window. See the Compare
/// function over in Symbol.
/// </remarks>
[Flags]
public enum DirectionFlags {
None = 0,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write
}
/// <summary>
/// I/O direction, used for memory-mapped I/O locations that have different meanings
/// (and hence different symbols) depending on whether they're read or written.
/// </summary>
public DirectionFlags Direction { get; private set; }
/// <summary>
/// Masks for symbols that represent multiple addresses. Usage:
/// <pre>
/// if ((addr & CompareMask) == CompareValue &&
/// (addr & AddressMask) == (Value & AddressMask)) {
/// // match!
/// }
/// </pre>
/// Instances are immutable.
/// </summary>
public class MultiAddressMask {
public int CompareMask { get; private set; }
public int CompareValue { get; private set; }
public int AddressMask { get; private set; }
public MultiAddressMask(int cmpMask, int cmpValue, int addrMask) {
CompareMask = cmpMask;
CompareValue = cmpValue;
AddressMask = addrMask;
}
public override string ToString() {
return "MultiAddrMask: cmpMask=$" + CompareMask.ToString("x4") +
" cmpValue=$" + CompareValue.ToString("x4") +
" addrMask=$" + AddressMask.ToString("x4");
}
}
/// <summary>
/// Bit masks to apply when performing comparisons. Useful when more than one address
/// maps to the same thing (e.g. Atari 2600 registers).
///
/// Will be null if no mask is specified.
/// </summary>
public MultiAddressMask MultiMask { get; private set; }
/// <summary>
/// Cross-reference data, generated by the analyzer.
/// </summary>
@ -89,15 +145,9 @@ namespace SourceGen {
/// </remarks>
public XrefSet Xrefs { get; private set; }
// NOTE: might be nice to identify the symbol's origin, e.g. which platform
// symbol file it was defined in. This could then be stored in a
// DisplayList line, for benefit of the Info panel.
// NOTE: if this also gets us the load order, we can properly select symbols
// by address when multiple platform symbols have the same value
/// <summary>
/// Internal base-object constructor, called by other constructors.
/// 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) {
@ -108,7 +158,9 @@ namespace SourceGen {
}
/// <summary>
/// Constructor. Limited form, used in a couple of places.
/// Constructor. Limited form, used in a couple of places, e.g. when we need to start
/// with a default value. The symbol will have unspecified width, ReadWrite direction,
/// and no mask.
/// </summary>
/// <param name="label">Symbol's label.</param>
/// <param name="value">Symbol's value.</param>
@ -118,8 +170,8 @@ 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,
string.Empty, string.Empty, -1, false) { }
: this(label, value, source, type, formatSubType, -1, false,
string.Empty, DirectionFlags.ReadWrite, null, string.Empty) { }
/// <summary>
/// Constructor. General form.
@ -130,14 +182,16 @@ namespace SourceGen {
/// <param name="type">Symbol type.</param>
/// <param name="formatSubType">Format descriptor sub-type, so we know how the
/// user wants the value to be displayed.</param>
/// <param name="comment">End-of-line comment.</param>
/// <param name="tag">Symbol tag, used for grouping platform symbols.</param>
/// <param name="width">Variable width.</param>
/// <param name="widthSpecified">True if width was explicitly specified. If this is
/// <param name="comment">End-of-line comment.</param>
/// <param name="direction">I/O direction.</param>
/// <param name="multiMask">Bit mask to apply before comparisons.</param>
/// <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, string comment, string tag, int width,
bool widthSpecified)
FormatDescriptor.SubType formatSubType, int width, bool widthSpecified,
string comment, DirectionFlags direction, MultiAddressMask multiMask, string tag)
: this(label, value, source, type) {
Debug.Assert(comment != null);
Debug.Assert(tag != null);
@ -156,6 +210,12 @@ namespace SourceGen {
DataDescriptor = FormatDescriptor.Create(width,
FormatDescriptor.Type.NumericLE, formatSubType);
Comment = comment;
Debug.Assert(((int)direction & ~(int)DirectionFlags.ReadWrite) == 0);
Direction = direction;
MultiMask = multiMask;
Tag = tag;
}
@ -167,49 +227,54 @@ namespace SourceGen {
/// to higher priority.</param>
/// <param name="fileIdent">Platform symbol file identifier, for the Info panel.</param>
public DefSymbol(string label, int value, Source source, Type type,
FormatDescriptor.SubType formatSubType, string comment, string tag, int width,
bool widthSpecified, int loadOrdinal, string fileIdent)
: this(label, value, source, type, formatSubType, comment, tag, width,
widthSpecified) {
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) {
LoadOrdinal = loadOrdinal;
FileIdentifier = fileIdent;
}
/// <summary>
/// Constructor. Used for deserialization, when we have a FormatDescriptor and a Symbol.
/// Create a DefSymbol given a Symbol, FormatDescriptor, and a few other things. Used
/// for deserialization.
/// </summary>
/// <param name="sym">Base symbol.</param>
/// <param name="dfd">Format descriptor.</param>
/// <param name="widthSpecified">Set if a width was explicitly specified.</param>
/// <param name="comment">End-of-line comment.</param>
public DefSymbol(Symbol sym, FormatDescriptor dfd, bool widthSpecified, string comment)
: this(sym.Label, sym.Value, sym.SymbolSource, sym.SymbolType) {
Debug.Assert(comment != null);
/// <param name="direction">I/O direction.</param>
/// <param name="multiMask">Bit mask to apply before comparisons.</param>
public static DefSymbol Create(Symbol sym, FormatDescriptor dfd, bool widthSpecified,
string comment, DirectionFlags direction, MultiAddressMask multiMask) {
int width = dfd.Length;
if (widthSpecified && sym.SymbolType == Type.Constant &&
sym.SymbolSource != Source.Variable) {
// non-variable constants don't have a width; override arg
Debug.WriteLine("Overriding constant DefSymbol width");
widthSpecified = false;
}
DataDescriptor = dfd;
HasWidth = widthSpecified;
Comment = comment;
Tag = string.Empty;
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);
}
/// <summary>
/// Constructs a DefSymbol from an existing DefSymbol, with a different label. Use
/// this to change the label while keeping everything else the same.
/// </summary>
/// <remarks>
/// This can't be a simple Rename() function that uses a copy constructor because
/// the label is in the base class.
/// </remarks>
/// <param name="defSym">Source DefSymbol.</param>
/// <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.Comment, defSym.Tag,
defSym.DataDescriptor.Length, defSym.HasWidth) { }
defSym.DataDescriptor.FormatSubType, defSym.DataDescriptor.Length,
defSym.HasWidth, defSym.Comment, defSym.Direction, defSym.MultiMask, defSym.Tag)
{ }
/// <summary>
/// Determines whether a symbol overlaps with a region. Useful for variables.
@ -274,6 +339,7 @@ namespace SourceGen {
public override string ToString() {
return base.ToString() + ":" + DataDescriptor + ";" + Comment +
" dir=" + Direction + " mask=" + (MultiMask == null ? "-" : MultiMask.ToString()) +
(string.IsNullOrEmpty(Tag) ? "" : " [" + Tag + "]");
}
}

View File

@ -1197,9 +1197,11 @@ namespace SourceGen {
Anattrib attr = mAnattribs[offset];
Symbol sym;
int address;
OpDef.MemoryEffect accType = OpDef.MemoryEffect.Unknown;
if (attr.IsInstructionStart && attr.DataDescriptor == null &&
attr.OperandAddress >= 0 && attr.OperandOffset < 0) {
// Has an operand address, but not an offset, meaning it's a reference
// This is an instruction that hasn't been explicitly formatted. It
// has an operand address, but not an offset, meaning it's a reference
// to an address outside the scope of the file. See if it has a
// platform symbol definition.
//
@ -1211,12 +1213,14 @@ namespace SourceGen {
// Using the full symbol table is potentially a tad less efficient than
// looking for a match exclusively in project/platform symbols, but it's
// the correct thing to do.
OpDef op = CpuDef.GetOpDef(FileData[offset]);
accType = op.MemEffect;
address = attr.OperandAddress;
sym = SymbolTable.FindNonVariableByAddress(address);
} else if ((attr.IsDataStart || attr.IsInlineDataStart) &&
attr.DataDescriptor != null && attr.DataDescriptor.IsNumeric &&
attr.DataDescriptor.FormatSubType == FormatDescriptor.SubType.Address) {
// Found a Numeric/Address item that matches. Data items don't have
// Found a Numeric/Address data item that matches. Data items don't have
// OperandAddress or OperandOffset set, so we need to check manually to
// see if the address falls within the project. In most situations this
// isn't really necessary, because the data analysis pass will have resolved
@ -1227,6 +1231,7 @@ namespace SourceGen {
address = RawData.GetWord(mFileData, offset, attr.DataDescriptor.Length,
attr.DataDescriptor.FormatType == FormatDescriptor.Type.NumericBE);
if (AddrMap.AddressToOffset(offset, address) < 0) {
accType = OpDef.MemoryEffect.ReadModifyWrite; // guess
sym = SymbolTable.FindNonVariableByAddress(address);
} else {
Debug.WriteLine("Found unhandled internal data addr ref at +" +

View File

@ -3377,10 +3377,23 @@ namespace SourceGen {
private void PopulateSymbolsList() {
mMainWin.SymbolsList.Clear();
foreach (Symbol sym in mProject.SymbolTable) {
string valueStr = mOutputFormatter.FormatHexValue(sym.Value, 0);
string sourceTypeStr = sym.SourceTypeString;
if (sym is DefSymbol) {
DefSymbol defSym = (DefSymbol)sym;
if (defSym.MultiMask != null) {
valueStr += " & " +
mOutputFormatter.FormatHexValue(defSym.MultiMask.AddressMask, 4);
}
if (defSym.Direction == DefSymbol.DirectionFlags.Read) {
sourceTypeStr += '<';
} else if (defSym.Direction == DefSymbol.DirectionFlags.Write) {
sourceTypeStr += '>';
}
}
MainWindow.SymbolsListItem sli = new MainWindow.SymbolsListItem(sym,
sym.SourceTypeString,
mOutputFormatter.FormatHexValue(sym.Value, 0),
sym.Label);
sourceTypeStr, valueStr, sym.Label);
mMainWin.SymbolsList.Add(sli);
}
}

View File

@ -31,22 +31,46 @@ namespace SourceGen {
public static readonly string FILENAME_FILTER = Res.Strings.FILE_FILTER_SYM65;
/// <summary>
/// Regex pattern for name/value pairs in symbol file.
/// Regex pattern for symbol definition in platform symbol file.
///
/// Alphanumeric ASCII + underscore for label, which must start at beginning of line.
/// Value is somewhat arbitrary, but ends if we see a comment delimiter (semicolon) or
/// whitespace. Width is decimal or hex. Spaces are allowed between tokens.
/// whitespace. Spaces are allowed between tokens, but not required. Value, width,
/// and mask may be hex, decimal, or binary.
///
/// Group 1 is the name, group 2 is '=' or '@', group 3 is the value, group 4 is
/// the symbol width (optional), group 5 is the comment (optional).
/// Looks like:
/// NAME {@,=,<,>} VALUE [& MASK] [WIDTH] [;COMMENT]
///
/// Regex output groups are:
/// 1. NAME
/// 2. type/direction char
/// 3. VALUE (can be any non-whitespace)
/// 4. optional: WIDTH (can be any non-whitespace)
/// 5. optional: COMMENT with leading ';'
/// </summary>
/// <remarks>
/// If you want to make sense of this, I highly recommend https://regex101.com/ .
/// </remarks>
private const string NAME_VALUE_PATTERN =
@"^([A-Za-z0-9_]+)\s*([@=])\s*([^\ ;]+)\s*([0-9\$]+)?\s*(;.*)?$";
private static Regex sNameValueRegex = new Regex(NAME_VALUE_PATTERN);
private const string SYMBOL_PATTERN =
@"^([A-Za-z0-9_]+)\s*([@=<>])\s*([^\s;]+)\s*([^\s;]+)?\s*(;.*)?$";
private static Regex sNameValueRegex = new Regex(SYMBOL_PATTERN);
private const int GROUP_NAME = 1;
private const int GROUP_TYPE = 2;
private const int GROUP_VALUE = 3;
private const int GROUP_WIDTH = 4;
private const int GROUP_COMMENT = 5;
/// <summary>
/// Regex pattern for mask definition in platform symbol file.
///
/// Looks like:
/// CMP_MASK CMP_VALUE ADDR_MASK [;COMMENT]
/// </summary>
private const string MULTI_MASK_PATTERN =
@"^\s*([^\s]+)\s*([^\s]+)\s*([^\s;]+)\s*(;.*)?$";
private static Regex sMaskRegex = new Regex(MULTI_MASK_PATTERN);
private const string MULTI_MASK_CMD = "*MULTI_MASK";
private const string TAG_CMD = "*TAG";
/// <summary>
@ -109,6 +133,7 @@ namespace SourceGen {
}
string tag = string.Empty;
DefSymbol.MultiAddressMask multiMask = null;
int lineNum = 0;
foreach (string line in lines) {
@ -118,42 +143,54 @@ namespace SourceGen {
} else if (line[0] == '*') {
if (line.StartsWith(TAG_CMD)) {
tag = ParseTag(line);
} else if (line.StartsWith(MULTI_MASK_CMD)) {
if (!ParseMask(line, out multiMask)) {
report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning,
Res.Strings.ERR_INVALID_MASK);
}
//Debug.WriteLine("Mask is now " + mask.ToString("x6"));
} else {
// Do something clever with *SYNOPSIS?
Debug.WriteLine("CMD: " + line);
Debug.WriteLine("Ignoring CMD: " + line);
}
} else {
MatchCollection matches = sNameValueRegex.Matches(line);
if (matches.Count == 1) {
//Debug.WriteLine("GOT '" + matches[0].Groups[1] + "' " +
// matches[0].Groups[2] + " '" + matches[0].Groups[3] + "'");
string label = matches[0].Groups[1].Value;
bool isConst = (matches[0].Groups[2].Value[0] == '=');
string label = matches[0].Groups[GROUP_NAME].Value;
char typeAndDir = matches[0].Groups[GROUP_TYPE].Value[0];
bool isConst = (typeAndDir == '=');
DefSymbol.DirectionFlags direction = DefSymbol.DirectionFlags.ReadWrite;
if (typeAndDir == '<') {
direction = DefSymbol.DirectionFlags.Read;
} else if (typeAndDir == '>') {
direction = DefSymbol.DirectionFlags.Write;
}
string badParseMsg;
int value, numBase;
bool parseOk;
if (isConst) {
// Allow various numeric options, and preserve the value.
parseOk = Asm65.Number.TryParseInt(matches[0].Groups[3].Value,
parseOk = Asm65.Number.TryParseInt(matches[0].Groups[GROUP_VALUE].Value,
out value, out numBase);
badParseMsg =
CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT;
} else {
// Allow things like "05/1000". Always hex.
numBase = 16;
parseOk = Asm65.Address.ParseAddress(matches[0].Groups[3].Value,
parseOk = Asm65.Address.ParseAddress(matches[0].Groups[GROUP_VALUE].Value,
(1 << 24) - 1, out value);
badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS;
}
int width = -1;
string widthStr = matches[0].Groups[4].Value;
string widthStr = matches[0].Groups[GROUP_WIDTH].Value;
if (parseOk && !string.IsNullOrEmpty(widthStr)) {
parseOk = Asm65.Number.TryParseInt(widthStr, out width,
out int ignoredBase);
if (parseOk) {
if (width < DefSymbol.MIN_WIDTH || width > DefSymbol.MAX_WIDTH) {
parseOk = false;
badParseMsg = Res.Strings.ERR_INVALID_WIDTH;
}
} else {
badParseMsg =
@ -161,11 +198,29 @@ namespace SourceGen {
}
}
if (parseOk && multiMask != null) {
// We need to ensure that all possible values fit within the mask.
// We don't test AddressValue here, because it's okay for the
// canonical value to be outside the masked range.
int testWidth = (width > 0) ? width : 1;
for (int testValue = value; testValue < value + testWidth; testValue++) {
if ((testValue & multiMask.CompareMask) != multiMask.CompareValue) {
parseOk = false;
badParseMsg = Res.Strings.ERR_VALUE_INCOMPATIBLE_WITH_MASK;
Debug.WriteLine("Mask FAIL: value=" + value.ToString("x6") +
" width=" + width +
" testValue=" + testValue.ToString("x6") +
" mask=" + multiMask);
break;
}
}
}
if (!parseOk) {
report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning,
badParseMsg);
} else {
string comment = matches[0].Groups[5].Value;
string comment = matches[0].Groups[GROUP_COMMENT].Value;
if (comment.Length > 0) {
// remove ';'
comment = comment.Substring(1);
@ -174,7 +229,8 @@ namespace SourceGen {
FormatDescriptor.GetSubTypeForBase(numBase);
DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform,
isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr,
subType, comment, tag, width, width > 0, loadOrdinal, fileIdent);
subType, width, width > 0, comment, direction, multiMask,
tag, loadOrdinal, fileIdent);
if (mSymbols.ContainsKey(label)) {
// This is very easy to do -- just define the same symbol twice
// in the same file. We don't really need to do anything about
@ -206,6 +262,54 @@ namespace SourceGen {
return tag;
}
/// <summary>
/// Parses the mask value out of a mask command line.
/// </summary>
/// <param name="line">Line to parse.</param>
/// <param name="multiMask">Parsed mask value, or null if the line was empty.</param>
/// <returns>True if the mask was parsed successfully.</returns>
private bool ParseMask(string line, out DefSymbol.MultiAddressMask multiMask) {
Debug.Assert(line.StartsWith(MULTI_MASK_CMD));
const int MIN = 0;
const int MAX = 0x00ffff;
multiMask = null;
string maskStr = line.Substring(MULTI_MASK_CMD.Length).Trim();
if (string.IsNullOrEmpty(maskStr)) {
// empty line, disable mask
return true;
}
MatchCollection matches = sMaskRegex.Matches(maskStr);
if (matches.Count != 1) {
return false;
}
string cmpMaskStr = matches[0].Groups[1].Value;
string cmpValueStr = matches[0].Groups[2].Value;
string addrMaskStr = matches[0].Groups[3].Value;
int cmpMask, cmpValue, addrMask, ignoredBase;
if (!Asm65.Number.TryParseInt(cmpMaskStr, out cmpMask, out ignoredBase) ||
cmpMask < MIN || cmpMask > MAX) {
Debug.WriteLine("Bad cmpMask: " + cmpMaskStr);
return false;
}
if (!Asm65.Number.TryParseInt(cmpValueStr, out cmpValue, out ignoredBase) ||
cmpValue < MIN || cmpValue > MAX) {
Debug.WriteLine("Bad cmpValue: " + cmpValueStr);
return false;
}
if (!Asm65.Number.TryParseInt(addrMaskStr, out addrMask, out ignoredBase) ||
addrMask < MIN || addrMask > MAX) {
Debug.WriteLine("Bad addrMask: " + addrMaskStr);
return false;
}
multiMask = new DefSymbol.MultiAddressMask(cmpMask, cmpValue, addrMask);
return true;
}
/// <summary>
/// One-off function to convert the IIgs toolbox function info from NList.Data.TXT
/// to .sym65 format. Doesn't really belong in here, but I'm too lazy to put it

View File

@ -310,17 +310,35 @@ namespace SourceGen {
Part = weakSym.ValuePart.ToString();
}
}
public class SerMultiMask {
public int CompareMask;
public int CompareValue;
public int AddressMask;
public SerMultiMask() { }
public SerMultiMask(DefSymbol.MultiAddressMask multiMask) {
CompareMask = multiMask.CompareMask;
CompareValue = multiMask.CompareValue;
AddressMask = multiMask.AddressMask;
}
}
public class SerDefSymbol : SerSymbol {
public SerFormatDescriptor DataDescriptor { get; set; }
public string Comment { get; set; }
public bool HasWidth { get; set; }
public string Direction { get; set; }
public SerMultiMask MultiMask { get; set; }
// Tag not relevant, Xrefs not recorded
public SerDefSymbol() { }
public SerDefSymbol(DefSymbol defSym) : base(defSym) {
DataDescriptor = new SerFormatDescriptor(defSym.DataDescriptor);
HasWidth = defSym.HasWidth;
Comment = defSym.Comment;
HasWidth = defSym.HasWidth;
Direction = defSym.Direction.ToString();
if (defSym.MultiMask != null) {
MultiMask = new SerMultiMask(defSym.MultiMask);
}
}
}
public class SerLocalVariableTable {
@ -741,8 +759,27 @@ namespace SourceGen {
out FormatDescriptor dfd)) {
return false;
}
DefSymbol.DirectionFlags direction;
if (string.IsNullOrEmpty(serDefSym.Direction)) {
direction = DefSymbol.DirectionFlags.ReadWrite;
} else try {
direction = (DefSymbol.DirectionFlags)
Enum.Parse(typeof(DefSymbol.DirectionFlags), serDefSym.Direction);
} catch (ArgumentException) {
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_DEF_SYMBOL_DIR +
": " + serDefSym.Direction);
return false;
}
outDefSym = new DefSymbol(sym, dfd, serDefSym.HasWidth, serDefSym.Comment);
DefSymbol.MultiAddressMask multiMask = null;
if (serDefSym.MultiMask != null) {
multiMask = new DefSymbol.MultiAddressMask(serDefSym.MultiMask.CompareMask,
serDefSym.MultiMask.CompareValue, serDefSym.MultiMask.AddressMask);
}
outDefSym = DefSymbol.Create(sym, dfd, serDefSym.HasWidth, serDefSym.Comment,
direction, multiMask);
return true;
}

View File

@ -43,6 +43,7 @@ limitations under the License.
<system:String x:Key="str_EquAddress">addr</system:String>
<system:String x:Key="str_EquConstant">const</system:String>
<system:String x:Key="str_EquStackRelative">stkrl</system:String>
<system:String x:Key="str_ErrBadDefSymbolDir">Unknown I/O direction in symbol</system:String>
<system:String x:Key="str_ErrBadFdFmt">Bad format descriptor at +{0:x6}.</system:String>
<system:String x:Key="str_ErrBadFdFormat">Bad format descriptor type</system:String>
<system:String x:Key="str_ErrBadFileLength">Bad file length</system:String>
@ -62,6 +63,8 @@ limitations under the License.
<system:String x:Key="str_ErrFileReadOnlyFmt">Cannot write to read-only file {0}.</system:String>
<system:String x:Key="str_ErrInvalidIntValue">Could not convert value to integer</system:String>
<system:String x:Key="str_ErrInvalidKeyValue">Key value is out of range</system:String>
<system:String x:Key="str_ErrInvalidMask">Invalid mask value</system:String>
<system:String x:Key="str_ErrInvalidWidth">Invalid width value</system:String>
<system:String x:Key="str_ErrInvalidSysdef" xml:space="preserve"> - INVALID DEFINITION</system:String>
<system:String x:Key="str_ErrLoadConfigFile">Unable to load config file</system:String>
<system:String x:Key="str_ErrNotProjectFile">This does not appear to be a valid .dis65 project file</system:String>
@ -69,6 +72,7 @@ limitations under the License.
<system:String x:Key="str_ErrProjectLoadFail">Unable to load project file</system:String>
<system:String x:Key="str_ErrProjectSaveFail">Unable to save project file</system:String>
<system:String x:Key="str_ErrTooLargeForPreview">[File was too large for preview window]</system:String>
<system:String x:Key="str_ErrValueIncompatibleWithMask">Symbol value is incompatible with multi-mask</system:String>
<system:String x:Key="str_ExternalFileBadDirFmt" xml:space="preserve">Symbol files and extension scripts must live in the application runtime directory ({0}) or project directory ({1}).&#x0d;&#x0d;File {2} lives elsewhere.</system:String>
<system:String x:Key="str_ExternalFileBadDirCaption">File Not In Runtime Directory</system:String>
<system:String x:Key="str_FileFilterAll">All files (*.*)|*.*</system:String>

View File

@ -67,6 +67,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_EquConstant");
public static string EQU_STACK_RELATIVE =
(string)Application.Current.FindResource("str_EquStackRelative");
public static string ERR_BAD_DEF_SYMBOL_DIR =
(string)Application.Current.FindResource("str_ErrBadDefSymbolDir");
public static string ERR_BAD_FD_FMT =
(string)Application.Current.FindResource("str_ErrBadFdFmt");
public static string ERR_BAD_FD_FORMAT =
@ -105,6 +107,10 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrInvalidIntValue");
public static string ERR_INVALID_KEY_VALUE =
(string)Application.Current.FindResource("str_ErrInvalidKeyValue");
public static string ERR_INVALID_MASK =
(string)Application.Current.FindResource("str_ErrInvalidMask");
public static string ERR_INVALID_WIDTH =
(string)Application.Current.FindResource("str_ErrInvalidWidth");
public static string ERR_INVALID_SYSDEF =
(string)Application.Current.FindResource("str_ErrInvalidSysdef");
public static string ERR_LOAD_CONFIG_FILE =
@ -119,6 +125,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrProjectSaveFail");
public static string ERR_TOO_LARGE_FOR_PREVIEW =
(string)Application.Current.FindResource("str_ErrTooLargeForPreview");
public static string ERR_VALUE_INCOMPATIBLE_WITH_MASK =
(string)Application.Current.FindResource("str_ErrValueIncompatibleWithMask");
public static string EXTERNAL_FILE_BAD_DIR_FMT =
(string)Application.Current.FindResource("str_ExternalFileBadDirFmt");
public static string EXTERNAL_FILE_BAD_DIR_CAPTION =

View File

@ -4,103 +4,115 @@
; Adapted from various online references. Comments are most directly from
; http://www.classic-games.com/atari2600/specs.html
;
; Some info from memory mirror maps by Chris Wilkson.
; Some info on memory mirror maps by Chris Wilkson.
*SYNOPSIS Atari 2600 (VCS) registers and constants
; 128 bytes of RIOT RAM is primarily $80-ff and $180-1ff
; mirror: $xy80
; x={even}
; y={0,1,4,5,8,9,C,D}
; 4K ROM is primarily $f000-ffff
; mirror: $x000
; x = {odd}
; mirror: $x000
; x = {odd}
;
; For 8K ROM, second bank is primarily $d000-dfff
; 128 bytes of RIOT RAM is primarily $80-ff and $180-1ff
; mirror: $xy80
; x={even}
; y={0,1,4,5,8,9,C,D}
; --> ???0 ??0? 1xxx xxxx
; TIA write registers
; mirror: $xyz0
; x = {even}
; y = {anything}
; z = {0, 4}
VSYNC @ $00 ;W 0000 00x0 Vertical Sync Set-Clear
VBLANK @ $01 ;W xx00 00x0 Vertical Blank Set-Clear
WSYNC @ $02 ;W ---- ---- Wait for Horizontal Blank
RSYNC @ $03 ;W ---- ---- Reset Horizontal Sync Counter
NUSIZ0 @ $04 ;W 00xx 0xxx Number-Size player/missle 0
NUSIZ1 @ $05 ;W 00xx 0xxx Number-Size player/missle 1
COLUP0 @ $06 ;W xxxx xxx0 Color-Luminance Player 0
COLUP1 @ $07 ;W xxxx xxx0 Color-Luminance Player 1
COLUPF @ $08 ;W xxxx xxx0 Color-Luminance Playfield
COLUBK @ $09 ;W xxxx xxx0 Color-Luminance Background
CTRLPF @ $0a ;W 00xx 0xxx Control Playfield, Ball, Collisions
REFP0 @ $0b ;W 0000 x000 Reflection Player 0
REFP1 @ $0c ;W 0000 x000 Reflection Player 1
PF0 @ $0d ;W xxxx 0000 Playfield Register Byte 0
PF1 @ $0e ;W xxxx xxxx Playfield Register Byte 1
PF2 @ $0f ;W xxxx xxxx Playfield Register Byte 2
RESP0 @ $10 ;W ---- ---- Reset Player 0
RESP1 @ $11 ;W ---- ---- Reset Player 1
RESM0 @ $12 ;W ---- ---- Reset Missle 0
RESM1 @ $13 ;W ---- ---- Reset Missle 1
RESBL @ $14 ;W ---- ---- Reset Ball
AUDC0 @ $15 ;W 0000 xxxx Audio Control 0
AUDC1 @ $16 ;W 0000 xxxx Audio Control 1
AUDF0 @ $17 ;W 000x xxxx Audio Frequency 0
AUDF1 @ $18 ;W 000x xxxx Audio Frequency 1
AUDV0 @ $19 ;W 0000 xxxx Audio Volume 0
AUDV1 @ $1a ;W 0000 xxxx Audio Volume 1
GRP0 @ $1b ;W xxxx xxxx Graphics Register Player 0
GRP1 @ $1c ;W xxxx xxxx Graphics Register Player 1
ENAM0 @ $1d ;W 0000 00x0 Graphics Enable Missle 0
ENAM1 @ $1e ;W 0000 00x0 Graphics Enable Missle 1
ENABL @ $1f ;W 0000 00x0 Graphics Enable Ball
HMP0 @ $20 ;W xxxx 0000 Horizontal Motion Player 0
HMP1 @ $21 ;W xxxx 0000 Horizontal Motion Player 1
HMM0 @ $22 ;W xxxx 0000 Horizontal Motion Missle 0
HMM1 @ $23 ;W xxxx 0000 Horizontal Motion Missle 1
HMBL @ $24 ;W xxxx 0000 Horizontal Motion Ball
VDELP0 @ $25 ;W 0000 000x Vertical Delay Player 0
VDELP1 @ $26 ;W 0000 000x Vertical Delay Player 1
VDELBL @ $27 ;W 0000 000x Vertical Delay Ball
RESMP0 @ $28 ;W 0000 00x0 Reset Missle 0 to Player 0
RESMP1 @ $29 ;W 0000 00x0 Reset Missle 1 to Player 1
HMOVE @ $2a ;W ---- ---- Apply Horizontal Motion
HMCLR @ $2b ;W ---- ---- Clear Horizontal Move Registers
CXCLR @ $2c ;W ---- ---- Clear Collision Latches
; mirror: $xyz0
; x = {even}
; y = {anything}
; z = {0, 4}
; --> ???0 ???? 0?xx xxxx
*MULTI_MASK %0001000010000000 %0000000000000000 %0000000000111111
VSYNC > $00 ;W 0000 00x0 Vertical Sync Set-Clear
VBLANK > $01 ;W xx00 00x0 Vertical Blank Set-Clear
WSYNC > $02 ;W ---- ---- Wait for Horizontal Blank
RSYNC > $03 ;W ---- ---- Reset Horizontal Sync Counter
NUSIZ0 > $04 ;W 00xx 0xxx Number-Size player/missle 0
NUSIZ1 > $05 ;W 00xx 0xxx Number-Size player/missle 1
COLUP0 > $06 ;W xxxx xxx0 Color-Luminance Player 0
COLUP1 > $07 ;W xxxx xxx0 Color-Luminance Player 1
COLUPF > $08 ;W xxxx xxx0 Color-Luminance Playfield
COLUBK > $09 ;W xxxx xxx0 Color-Luminance Background
CTRLPF > $0a ;W 00xx 0xxx Control Playfield, Ball, Collisions
REFP0 > $0b ;W 0000 x000 Reflection Player 0
REFP1 > $0c ;W 0000 x000 Reflection Player 1
PF0 > $0d ;W xxxx 0000 Playfield Register Byte 0
PF1 > $0e ;W xxxx xxxx Playfield Register Byte 1
PF2 > $0f ;W xxxx xxxx Playfield Register Byte 2
RESP0 > $10 ;W ---- ---- Reset Player 0
RESP1 > $11 ;W ---- ---- Reset Player 1
RESM0 > $12 ;W ---- ---- Reset Missle 0
RESM1 > $13 ;W ---- ---- Reset Missle 1
RESBL > $14 ;W ---- ---- Reset Ball
AUDC0 > $15 ;W 0000 xxxx Audio Control 0
AUDC1 > $16 ;W 0000 xxxx Audio Control 1
AUDF0 > $17 ;W 000x xxxx Audio Frequency 0
AUDF1 > $18 ;W 000x xxxx Audio Frequency 1
AUDV0 > $19 ;W 0000 xxxx Audio Volume 0
AUDV1 > $1a ;W 0000 xxxx Audio Volume 1
GRP0 > $1b ;W xxxx xxxx Graphics Register Player 0
GRP1 > $1c ;W xxxx xxxx Graphics Register Player 1
ENAM0 > $1d ;W 0000 00x0 Graphics Enable Missle 0
ENAM1 > $1e ;W 0000 00x0 Graphics Enable Missle 1
ENABL > $1f ;W 0000 00x0 Graphics Enable Ball
HMP0 > $20 ;W xxxx 0000 Horizontal Motion Player 0
HMP1 > $21 ;W xxxx 0000 Horizontal Motion Player 1
HMM0 > $22 ;W xxxx 0000 Horizontal Motion Missle 0
HMM1 > $23 ;W xxxx 0000 Horizontal Motion Missle 1
HMBL > $24 ;W xxxx 0000 Horizontal Motion Ball
VDELP0 > $25 ;W 0000 000x Vertical Delay Player 0
VDELP1 > $26 ;W 0000 000x Vertical Delay Player 1
VDELBL > $27 ;W 0000 000x Vertical Delay Ball
RESMP0 > $28 ;W 0000 00x0 Reset Missle 0 to Player 0
RESMP1 > $29 ;W 0000 00x0 Reset Missle 1 to Player 1
HMOVE > $2a ;W ---- ---- Apply Horizontal Motion
HMCLR > $2b ;W ---- ---- Clear Horizontal Move Registers
CXCLR > $2c ;W ---- ---- Clear Collision Latches
; $2d-3f undefined, but $3e/3f may be used for bank switching
; TIA read registers. The actual values are $0-d, but only 4 bits of the
; address are used on the hardware. I'm using $3x because that's what
; Adventure did, but it might be necessary to fiddle with this.
CXM0P @ $30 ;R xx00 0000 Read Collision M0-P1 M0-P0
CXM1P @ $31 ;R xx00 0000 Read Collision M1-P0 M1-P1
CXP0FB @ $32 ;R xx00 0000 Read Collision P0-PF P0-BL
CXP1FB @ $33 ;R xx00 0000 Read Collision P1-PF P1-BL
CXM0FB @ $34 ;R xx00 0000 Read Collision M0-PF M0-BL
CXM1FB @ $35 ;R xx00 0000 Read Collision M1-PF M1-BL
CXBLPF @ $36 ;R x000 0000 Read Collision BL-PF -----
CXPPMM @ $37 ;R xx00 0000 Read Collision P0-P1 M0-M1
INPT0 @ $38 ;R x000 0000 Read Pot Port 0
INPT1 @ $39 ;R x000 0000 Read Pot Port 1
INPT2 @ $3a ;R x000 0000 Read Pot Port 2
INPT3 @ $3b ;R x000 0000 Read Pot Port 3
INPT4 @ $3c ;R x000 0000 Read Input (Trigger) 0
INPT5 @ $3d ;R x000 0000 Read Input (Trigger) 1
; TIA read registers. Same basic area as the write registers, but
; only the low 4 bits matter. I'm using $3x as the canonical value
; because that's what Adventure did.
; --> ???0 ???? 0??? xxxx
*MULTI_MASK %0001000010000000 %0000000000000000 %0000000000001111
CXM0P < $30 ;R xx00 0000 Read Collision M0-P1 M0-P0
CXM1P < $31 ;R xx00 0000 Read Collision M1-P0 M1-P1
CXP0FB < $32 ;R xx00 0000 Read Collision P0-PF P0-BL
CXP1FB < $33 ;R xx00 0000 Read Collision P1-PF P1-BL
CXM0FB < $34 ;R xx00 0000 Read Collision M0-PF M0-BL
CXM1FB < $35 ;R xx00 0000 Read Collision M1-PF M1-BL
CXBLPF < $36 ;R x000 0000 Read Collision BL-PF -----
CXPPMM < $37 ;R xx00 0000 Read Collision P0-P1 M0-M1
INPT0 < $38 ;R x000 0000 Read Pot Port 0
INPT1 < $39 ;R x000 0000 Read Pot Port 1
INPT2 < $3a ;R x000 0000 Read Pot Port 2
INPT3 < $3b ;R x000 0000 Read Pot Port 3
INPT4 < $3c ;R x000 0000 Read Input (Trigger) 0
INPT5 < $3d ;R x000 0000 Read Input (Trigger) 1
; PIA/RIOT (6532) registers
; mirror: $xyz0
; x = {even}
; y = {2,3,6,7,a,b,e,f}
; z = {8,a,c,e}
; mirror: $xyz0
; x = {even}
; y = {2,3,6,7,a,b,e,f}
; z = {8,a,c,e}
; --> ???0 ??1? 1??x xxxx
*MULTI_MASK %0001001010000000 %0000001010000000 %0000000000011111
SWCHA @ $280 ;RW Port A data register (joysticks...)
SWACNT @ $281 ;RW Port A data direction register (DDR)
SWCHB @ $282 ;RW Port B data (console switches)
SWBCNT @ $283 ;RW Port B DDR
INTIM @ $284 ;R Timer output
SWBCNT @ $283 ;RW Port B data direction register (DDR)
INTIM < $284 ;R Timer output
TIM1T > $294 ;W set 1 clock interval
TIM8T > $295 ;W set 8 clock interval
TIM64T > $296 ;W set 64 clock interval
T1024T > $297 ;W set 1024 clock interval
*MULTI_MASK
TIM1T @ $294 ;W set 1 clock interval
TIM8T @ $295 ;W set 8 clock interval
TIM64T @ $296 ;W set 64 clock interval
T1024T @ $297 ;W set 1024 clock interval

View File

@ -181,6 +181,17 @@ namespace SourceGen {
case SymbolSortField.CombinedType:
if (isAscending) {
int cmp = string.Compare(a.SourceTypeString, b.SourceTypeString);
if (cmp == 0) {
int aDir = 0;
int bDir = 0;
if (a is DefSymbol) {
aDir = (int)((DefSymbol)a).Direction;
}
if (b is DefSymbol) {
bDir = (int)((DefSymbol)b).Direction;
}
cmp = aDir - bDir;
}
if (cmp == 0) {
cmp = string.Compare(a.Label, b.Label);
}

View File

@ -18,6 +18,73 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
/*
A few words about by-value lookups.
We guarantee that symbol labels are unique, but multiple symbols can have the same value.
This becomes interesting when we're trying to match external address references to symbols
defined in a platform file or the project properties. It becomes especially interesting
when the symbols have widths larger than 1, and partially overlap each other.
For deterministic behavior it's necessary to define a priority order in event of overlap.
The basic rules for determining the symbol associated with a given address are:
1. Newer platform symbol definitions replace older definitions, at file granularity.
2. As an extension of rule #1, project symbols override platform symbols.
3. User symbols override both platform and project symbols. They can't overlap by value,
since one is internal and the others are external, but if a user symbol's label matches
a project/platform symbol, the project/platform symbol will be hidden.
4. If two multi-byte symbol definitions overlap, we use whichever was defined closest
to the actual address while still appearing before it. So if we have FOO=$2000/10 and
BAR=$2005/10, $2004 would be FOO and $2005 would be BAR. (Note this can only happen for
two symbols inside the same platform file or in the project symbol definitions; otherwise
one of the previous rules would have determined it.)
5. If everything else is equal, e.g. we have FOO=$2000 and BAR=$2000 in the same file,
the winner is determined alphabetically. (We don't track symbol definition line numbers,
and there's no definite order in project properties, so there's not much else to do.)
Working through the math on every access could get painful, so we create a dictionary with
the value as the key, and add symbols to it as we work our way through that platform and
project files. Every address is represented, so a label with a width of 10 would have 10
entries in the dictionary. If we're adding a symbol at an address that already has an entry,
we do a priority check, and either leave it alone or replace it with the new value.
For 8-bit code it would be slightly more efficient to use a 64K array representing all of
memory, but that doesn't scale for 65816. That said, we probably want to break it up by
bank anyway to allow for partial updates.
----------
A few words about address masks.
On the Atari 2600, you can access registers, RAM, and ROM from multiple addresses. For
example, the first TIA register can be accessed at $0000, $0040, $0100, $0140, and so on,
but only in "even" 4K pages ($0000, $2000, $4000, ...). Because the underlying hardware is
just watching for specific values on certain address lines, the set of matching addresses can
be described with a pair of bit masks, plus one more mask to define which lines are relevant.
The question is how to handle a by-address lookup here. There are two basic approaches:
1. Add all possible entries to the dictionary.
2. Maintain a separate list of masked symbols, and match against those.
Option #1 makes adding a symbol expensive, but lookups very cheap. We have to add
potentially thousands of entries to the dictionary for each masked symbol. When we want
to look up a symbol, though, we don't have to do anything different.
Option #2 makes adding a symbol cheap, but lookups are problematic. The problem arises if
a masked symbol overlaps with a non-masked symbol. If we want the priority to work the way
we described earlier, some non-masked symbols might have priority over a given masked symbol
while others don't.
(I kinda feel like I'm solving problems I don't have, but consistent behavior is a Good Thing.)
It's possible to mitigate the problems of both with a hybrid approach:
- Non-masked symbols get added to the dictionary as usual.
- Masked symbols are compared to all dictionary entries. If the mask matches, the
existing entry is kept or replaced according to the usual rules.
*/
namespace SourceGen {
/// <summary>
/// List of all symbols, arranged primarily by label, but also accessible by value. All
@ -41,17 +108,7 @@ namespace SourceGen {
/// For efficiency on larger data files, we may want to break this up by bank. That
/// way we can do a partial update.
/// </remarks>
private Dictionary<int, Symbol> mSymbolsByAddress =
new Dictionary<int, Symbol>();
/// <summary>
/// This is incremented whenever the contents of the symbol table change. External
/// code can compare this against a previous value to see if anything has changed
/// since the last visit.
///
/// We could theoretically miss something at the 2^32 rollover. Not worried.
/// </summary>
//public int ChangeSerial { get; private set; }
private Dictionary<int, Symbol> mSymbolsByAddress = new Dictionary<int, Symbol>();
public SymbolTable() { }

View File

@ -23,12 +23,14 @@ limitations under the License.
xmlns:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Edit Symbol"
SizeToContent="Height" Width="340" ResizeMode="NoResize"
SizeToContent="Height" Width="350" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded"
ContentRendered="Window_ContentRendered">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<system:String x:Key="str_WidthLimitFmt">• Decimal or hex value, 1-{0}</system:String>
<system:String x:Key="str_ProjectConstant">Constant</system:String>
<system:String x:Key="str_VariableConstant">Stack-Relative Offset</system:String>
@ -46,51 +48,72 @@ limitations under the License.
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Label:"/>
<StackPanel Grid.Column="1" Grid.Row="0" Margin="0,0,0,16">
<TextBlock Grid.Column="0" Grid.Row="0" Margin="0,0,8,0">Symbol type:</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal" Margin="0,1,0,12"
IsEnabled="{Binding NotReadOnlyValueAndType}">
<RadioButton Content="Address" GroupName="Type" Margin="0,0,0,0" IsChecked="{Binding IsAddress}"/>
<RadioButton Content="{Binding ConstantLabel, FallbackValue=Constant}" Margin="8,0,0,0"
GroupName="Type" IsChecked="{Binding IsConstant}"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Label:"/>
<StackPanel Grid.Column="1" Grid.Row="1" Margin="0,0,0,16">
<TextBox Name="labelTextBox" Margin="0,1,0,0" Text="{Binding Label, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Name="labelNotesLabel" Text="• 2+ alphanumerics, starting with letter" Margin="0,4,0,0"/>
<TextBlock Name="projectLabelUniqueLabel" Text="• Unique among project symbols" Margin="0,4,0,0"/>
<TextBlock Name="labelUniqueLabel" Text="• Unique" Margin="0,4,0,0"/>
<TextBlock Name="projectLabelUniqueLabel" Text="• Unique among project symbols" Margin="0,4,0,0"
Visibility="{Binding IsNotVariable, Converter={StaticResource BoolToVis}}"/>
<TextBlock Name="labelUniqueLabel" Text="• Unique" Margin="0,4,0,0"
Visibility="{Binding IsVariable, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Value:"/>
<StackPanel Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Margin="0,0,0,16">
<TextBlock Grid.Column="0" Grid.Row="2" Text="Value:"/>
<StackPanel Grid.Column="1" Grid.Row="2" VerticalAlignment="Top" Margin="0,0,0,16">
<TextBox Margin="0,1,0,0" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"
IsReadOnly="{Binding ReadOnlyValueAndType}"/>
<TextBlock Name="varValueRangeLabel" Text="• Value between 0-255, including width" Margin="0,4,0,0"/>
<TextBlock Name="varValueUniqueLabel" Text="• Values in table must not overlap" Margin="0,4,0,0"/>
<TextBlock Name="varValueRangeLabel" Text="• Value between 0-255, including width" Margin="0,4,0,0"
Visibility="{Binding IsVariable, Converter={StaticResource BoolToVis}}"/>
<TextBlock Name="varValueUniqueLabel" Text="• Values in table must not overlap" Margin="0,4,0,0"
Visibility="{Binding IsVariable, Converter={StaticResource BoolToVis}}"/>
<TextBlock Name="valueNotesLabel" Text="• Decimal, hex ($), or binary (%)" Margin="0,4,0,0"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Width:"/>
<StackPanel Grid.Column="1" Grid.Row="2" Margin="0,0,0,16">
<TextBlock Grid.Column="0" Grid.Row="3" Text="Width:"/>
<StackPanel Grid.Column="1" Grid.Row="3" Margin="0,0,0,16">
<TextBox Margin="0,1,0,0" Text="{Binding VarWidth, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"/>
FontFamily="{StaticResource GeneralMonoFont}"
IsEnabled="{Binding IsAddress}"/>
<TextBlock Name="widthNotesLabel" Margin="0,4,0,0"
Text="{Binding WidthLimitLabel, FallbackValue=• Decimal or hex value\, 1-ZZZZZ}"/>
<TextBlock Name="widthOptionalLabel" Text="• Optional for Address, ignored for Constant" Margin="0,4,0,0"/>
<TextBlock Name="widthOptionalLabel" Text="• Optional for Address, ignored for Constant" Margin="0,4,0,0"
Visibility="{Binding IsNotVariable, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Comment:" Margin="0,0,8,0"/>
<StackPanel Grid.Column="1" Grid.Row="3" Margin="0,0,0,16">
<TextBlock Grid.Column="0" Grid.Row="4" Text="Comment:" Margin="0,0,8,0"/>
<StackPanel Grid.Column="1" Grid.Row="4" Margin="0,0,0,16">
<TextBox Margin="0,1,0,0" Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}" ScrollViewer.CanContentScroll="True"/>
<TextBlock Text="• Optional" Margin="0,4,0,0"/>
</StackPanel>
<GroupBox Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="2" Header="Symbol Type" Padding="4">
<StackPanel Orientation="Horizontal" IsEnabled="{Binding NotReadOnlyValueAndType}">
<RadioButton Content="Address" IsChecked="{Binding IsAddress}"/>
<RadioButton Content="{Binding ConstantLabel, FallbackValue=Constant}" Margin="24,0,0,0" IsChecked="{Binding IsConstant}"/>
<TextBlock Grid.Column="0" Grid.Row="5"
Visibility="{Binding IsNotVariable, Converter={StaticResource BoolToVis}}"
Text="Access:"/>
<StackPanel Grid.Column="1" Grid.Row="5" Margin="0,0,0,4"
Visibility="{Binding IsNotVariable, Converter={StaticResource BoolToVis}}">
<StackPanel Orientation="Horizontal" Margin="0,1,0,0"
IsEnabled="{Binding IsAddress}">
<CheckBox Content="Read" Margin="0,0,0,0" IsChecked="{Binding IsReadChecked}"/>
<CheckBox Content="Write" Margin="24,0,0,0" IsChecked="{Binding IsWriteChecked}"/>
</StackPanel>
</GroupBox>
<TextBlock Name="checkReadWriteLabel" Text="• Check one or both for Address" Margin="0,4,0,0"/>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="5" Margin="0,16,0,0"
<StackPanel Grid.Column="1" Grid.Row="6" Margin="0,16,0,0"
Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="OK" Width="70" IsDefault="True" IsEnabled="{Binding IsValid}"
Click="OkButton_Click"/>

View File

@ -91,6 +91,18 @@ namespace SourceGen.WpfGui {
}
private string mConstantLabel;
public bool IsReadChecked {
get { return mIsReadChecked; }
set { mIsReadChecked = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mIsReadChecked;
public bool IsWriteChecked {
get { return mIsWriteChecked; }
set { mIsWriteChecked = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mIsWriteChecked;
public bool ReadOnlyValueAndType {
get { return mReadOnlyValueAndType; }
set { mReadOnlyValueAndType = value; OnPropertyChanged(); }
@ -100,6 +112,18 @@ namespace SourceGen.WpfGui {
}
private bool mReadOnlyValueAndType;
/// <summary>
/// Set to true if we should create a Variable rather than a project symbol.
/// </summary>
public bool IsVariable {
get { return mIsVariable; }
set { mIsVariable = value; OnPropertyChanged(); }
}
public bool IsNotVariable {
get { return !mIsVariable; }
}
private bool mIsVariable;
/// <summary>
/// Format object to use when formatting addresses and constants.
/// </summary>
@ -120,11 +144,6 @@ namespace SourceGen.WpfGui {
/// </summary>
private SymbolTable mSymbolTable;
/// <summary>
/// Set to true if we should create a Variable rather than a project symbol.
/// </summary>
private bool mIsVariable;
/// <summary>
/// Set to true if the width value is optional.
/// </summary>
@ -166,21 +185,16 @@ namespace SourceGen.WpfGui {
mDefSymbolList = defList;
mOldSym = defSym;
mSymbolTable = symbolTable;
mIsVariable = isVariable;
IsVariable = isVariable;
mReadOnlyValueAndType = lockValueAndType;
Label = Value = VarWidth = Comment = string.Empty;
// hide the inappropriate stuff
int maxWidth;
if (isVariable) {
projectLabelUniqueLabel.Visibility = widthOptionalLabel.Visibility =
Visibility.Collapsed;
ConstantLabel = (string)FindResource("str_VariableConstant");
maxWidth = 256;
} else {
labelUniqueLabel.Visibility = varValueRangeLabel.Visibility =
varValueUniqueLabel.Visibility = Visibility.Collapsed;
ConstantLabel = (string)FindResource("str_ProjectConstant");
maxWidth = 65536;
}
@ -207,11 +221,18 @@ namespace SourceGen.WpfGui {
} else {
IsAddress = true;
}
if (mOldSym.Direction == DefSymbol.DirectionFlags.Read) {
IsReadChecked = true;
} else if (mOldSym.Direction == DefSymbol.DirectionFlags.Write) {
IsWriteChecked = true;
} else {
IsReadChecked = IsWriteChecked = true;
}
} else {
IsAddress = true;
IsAddress = IsReadChecked = IsWriteChecked = true;
}
labelTextBox.Focus();
UpdateControls();
}
@ -246,7 +267,7 @@ namespace SourceGen.WpfGui {
labelUnique = !mSymbolTable.TryGetValue(Label, out Symbol sym);
// It's okay if this and the other are both variables.
if (!labelUnique && mIsVariable && sym.IsVariable) {
if (!labelUnique && IsVariable && sym.IsVariable) {
labelUnique = true;
}
}
@ -261,14 +282,14 @@ namespace SourceGen.WpfGui {
bool widthValid = true;
int thisWidth = -1;
if (IsConstant && !mIsVariable) {
if (IsConstant && !IsVariable) {
// width field is ignored
} else if (string.IsNullOrEmpty(VarWidth)) {
// blank field is okay if the width is optional
widthValid = mIsWidthOptional;
} else if (!Asm65.Number.TryParseInt(VarWidth, out thisWidth, out int unusedBase) ||
thisWidth < DefSymbol.MIN_WIDTH || thisWidth > DefSymbol.MAX_WIDTH ||
(mIsVariable && thisWidth > 256)) {
(IsVariable && thisWidth > 256)) {
// All widths must be between 1 and 65536. For a variable, the full thing must
// fit on zero page without wrapping. We test for 256 here so that we highlight
// the "bad width" label, rather than the "it doesn't fit on the page" label.
@ -276,7 +297,7 @@ namespace SourceGen.WpfGui {
}
bool valueRangeValid = true;
if (mIsVariable && valueValid && widthValid) {
if (IsVariable && valueValid && widthValid) {
// $ff with width 1 is okay, $ff with width 2 is not
if (thisValue < 0 || thisValue + thisWidth > 256) {
valueRangeValid = false;
@ -288,7 +309,7 @@ namespace SourceGen.WpfGui {
// For a variable, the value must also be unique within the table. Values have
// width, so we need to check for overlap.
bool valueUniqueValid = true;
if (mIsVariable && valueValid && widthValid) {
if (IsVariable && valueValid && widthValid) {
foreach (KeyValuePair<string, DefSymbol> kvp in mDefSymbolList) {
if (kvp.Value != mOldSym &&
DefSymbol.CheckOverlap(kvp.Value, thisValue, thisWidth, symbolType)) {
@ -298,6 +319,11 @@ namespace SourceGen.WpfGui {
}
}
bool rwValid = true;
if (!IsVariable && IsAddress) {
rwValid = IsReadChecked || IsWriteChecked;
}
labelNotesLabel.Foreground = labelValid ? mDefaultLabelColor : Brushes.Red;
labelUniqueLabel.Foreground = projectLabelUniqueLabel.Foreground =
labelUnique ? mDefaultLabelColor : Brushes.Red;
@ -305,9 +331,10 @@ namespace SourceGen.WpfGui {
varValueRangeLabel.Foreground = valueRangeValid ? mDefaultLabelColor : Brushes.Red;
varValueUniqueLabel.Foreground = valueUniqueValid ? mDefaultLabelColor : Brushes.Red;
widthNotesLabel.Foreground = widthValid ? mDefaultLabelColor : Brushes.Red;
checkReadWriteLabel.Foreground = rwValid ? mDefaultLabelColor : Brushes.Red;
IsValid = labelValid && labelUnique && valueValid && valueRangeValid &&
valueUniqueValid && widthValid;
valueUniqueValid && widthValid && rwValid;
}
private bool ParseValue(out int value, out int numBase) {
@ -325,17 +352,29 @@ namespace SourceGen.WpfGui {
ParseValue(out int value, out int numBase);
FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase);
int width = -1;
if (IsConstant && !mIsVariable) {
if (IsConstant && !IsVariable) {
// width field is ignored, don't bother parsing
} else if (!string.IsNullOrEmpty(VarWidth)) {
bool ok = Asm65.Number.TryParseInt(VarWidth, out width, out int unusedNumBase);
Debug.Assert(ok);
}
DefSymbol.DirectionFlags direction;
if (IsReadChecked && IsWriteChecked) {
direction = DefSymbol.DirectionFlags.ReadWrite;
} else if (IsReadChecked) {
direction = DefSymbol.DirectionFlags.Read;
} else if (IsWriteChecked) {
direction = DefSymbol.DirectionFlags.Write;
} else {
Debug.Assert(false);
direction = DefSymbol.DirectionFlags.None;
}
NewSym = new DefSymbol(Label, value,
mIsVariable ? Symbol.Source.Variable : Symbol.Source.Project,
IsVariable ? Symbol.Source.Variable : Symbol.Source.Project,
IsConstant ? Symbol.Type.Constant : Symbol.Type.ExternalAddr,
subType, Comment, string.Empty, width, width > 0);
subType, width, width > 0, Comment, direction, null, string.Empty);
DialogResult = true;
}

View File

@ -1146,7 +1146,8 @@ namespace SourceGen.WpfGui {
// dialog will handle it.
initialVar = new DefSymbol("VAR", mOperandValue,
Symbol.Source.Variable, symType, FormatDescriptor.SubType.None,
string.Empty, string.Empty, 1, true);
1, true, string.Empty, DefSymbol.DirectionFlags.ReadWrite,
null, string.Empty);
}
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,