1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-29 10:50:28 +00:00

Allow explicit widths in project/platform symbols, part 1

The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.

The first step is to allow widths to be specified in platform files,
and set with the project symbol editor.  The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants.  (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)

We also now show the symbol's type (address or constant) and width
in the listing.  This gets really distracting when overused, so we
only show it when the width is explicitly set.  The default width
is 1, which most things will be, so users can make an aesthetic
choice there.  (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)

The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page.  The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.

The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.

Widths have been added to a handful of Apple II platform defs.
This commit is contained in:
Andy McFadden 2019-10-01 14:58:24 -07:00
parent 7ddde3aad7
commit 2a41d70e04
18 changed files with 278 additions and 109 deletions

View File

@ -19,7 +19,8 @@ using System.Diagnostics;
namespace Asm65 {
public class Number {
/// <summary>
/// Parses an integer in a variety of formats (hex, decimal, binary).
/// Parses an integer in a variety of formats (hex, decimal, binary). We allow
/// hex to be identified with a leading '$' as well as "0x".
///
/// Trim whitespace before calling here.
/// </summary>

View File

@ -18,25 +18,39 @@ using System.Diagnostics;
namespace SourceGen {
/// <summary>
/// Subclass of Symbol used for symbols defined in the platform or project.
/// Subclass of Symbol used for symbols defined in a platform symbol file, in the project
/// symbol table, or in a local variable table.
///
/// Instances are immutable, except for the Xrefs field.
/// </summary>
/// <remarks>
/// The Xrefs field isn't really part of the object. It's just convenient to reference
/// The Xrefs field isn't really part of the object. It's just convenient to access
/// them from here.
/// </remarks>
public class DefSymbol : Symbol {
// width to use when width doesn't matter; use 1 to try to get a prefab object
public const int NO_WIDTH = 1;
// Absolute min/max width. Zero-page variables are more limited, because they're not
// allowed to wrap around the end of the page.
public const int MIN_WIDTH = 1;
public const int MAX_WIDTH = 8;
public const int MAX_WIDTH = 65536;
// Value to pass to the FormatDescriptor when no width is given.
private const int DEFAULT_WIDTH = 1;
/// <summary>
/// Data format descriptor.
/// </summary>
public FormatDescriptor DataDescriptor { get; private set; }
/// <summary>
/// True if a width was specified for this symbol.
/// </summary>
/// <remarks>
/// All symbols have a positive width, stored in the FormatDescriptor Length property.
/// We may not want to display widths that haven't been explicitly set, however, so we
/// keep track here.
/// </remarks>
public bool HasWidth { get; private set; }
/// <summary>
/// User-supplied comment.
/// </summary>
@ -65,6 +79,8 @@ namespace SourceGen {
// 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>
@ -79,7 +95,7 @@ namespace SourceGen {
}
/// <summary>
/// Constructor.
/// Constructor. Limited form, used in a couple of places.
/// </summary>
/// <param name="label">Symbol's label.</param>
/// <param name="value">Symbol's value.</param>
@ -87,14 +103,13 @@ 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>
public DefSymbol(string label, int value, Source source, Type type,
FormatDescriptor.SubType formatSubType, string comment, string tag)
: this(label, value, source, type, formatSubType, comment, tag, NO_WIDTH) { }
FormatDescriptor.SubType formatSubType)
: this(label, value, source, type, formatSubType,
string.Empty, string.Empty, -1, false) { }
/// <summary>
/// Constructor. Used for local variables, which have a meaningful width.
/// Constructor. General form.
/// </summary>
/// <param name="label">Symbol's label.</param>
/// <param name="value">Symbol's value.</param>
@ -105,31 +120,52 @@ namespace SourceGen {
/// <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
/// 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)
FormatDescriptor.SubType formatSubType, string comment, string tag, int width,
bool widthSpecified)
: this(label, value, source, type) {
Debug.Assert(comment != null);
Debug.Assert(tag != null);
if (widthSpecified && type == Type.Constant && source != Source.Variable) {
// non-variable constants don't have a width; override arg
Debug.WriteLine("Overriding constant DefSymbol width");
widthSpecified = false;
}
HasWidth = widthSpecified;
if (!widthSpecified) {
width = DEFAULT_WIDTH;
}
Debug.Assert(width >= MIN_WIDTH && width <= MAX_WIDTH);
DataDescriptor = FormatDescriptor.Create(width,
FormatDescriptor.Type.NumericLE, formatSubType);
Comment = comment;
Tag = tag;
}
/// <summary>
/// Constructs a DefSymbol from a Symbol and a format descriptor. This is used
/// for project symbols.
/// Constructor. Used for deserialization, when we have a FormatDescriptor and a Symbol.
/// </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, string comment)
public DefSymbol(Symbol sym, FormatDescriptor dfd, bool widthSpecified, string comment)
: this(sym.Label, sym.Value, sym.SymbolSource, sym.SymbolType) {
Debug.Assert(comment != null);
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;
}
@ -143,7 +179,7 @@ namespace SourceGen {
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.DataDescriptor.Length, defSym.HasWidth) { }
/// <summary>
/// Determines whether a symbol overlaps with a region. Useful for variables.

View File

@ -162,7 +162,7 @@ namespace SourceGen {
/// <summary>
/// Constructor for base type data item.
/// </summary>
/// <param name="Length">Length, in bytes.</param>
/// <param name="length">Length, in bytes.</param>
/// <param name="fmt">Format type.</param>
/// <param name="subFmt">Format sub-type.</param>
private FormatDescriptor(int length, Type fmt, SubType subFmt) {

View File

@ -840,6 +840,7 @@ namespace SourceGen {
string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
null, defSym.DataDescriptor, defSym.Value, 1,
PseudoOp.FormatNumericOpFlags.None);
valueStr = PseudoOp.AnnotateEquDirective(formatter, valueStr, defSym);
string comment = formatter.FormatEolComment(defSym.Comment);
FormattedParts parts = FormattedParts.CreateEquDirective(defSym.Label,
formatter.FormatPseudoOp(opNames.EquDirective),
@ -1344,6 +1345,7 @@ namespace SourceGen {
string addrStr = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable,
null, defSym.DataDescriptor, defSym.Value, 1,
PseudoOp.FormatNumericOpFlags.None);
addrStr = PseudoOp.AnnotateEquDirective(mFormatter, addrStr, defSym);
string comment = mFormatter.FormatEolComment(defSym.Comment);
return FormattedParts.CreateEquDirective(
mFormatter.FormatVariableLabel(defSym.Label),

View File

@ -34,23 +34,23 @@ namespace SourceGen {
/// Regex pattern for name/value pairs in 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).
/// Spaces are allowed between tokens.
/// 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.
///
/// Group 1 is the name, group 2 is '=' or '@', group 3 is the value, group 4 is
/// the comment (optional).
/// the symbol width (optional), group 5 is the comment (optional).
/// </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*(;.*)?$";
@"^([A-Za-z0-9_]+)\s*([@=])\s*([^\ ;]+)\s*([0-9\$]+)?\s*(;.*)?$";
private static Regex sNameValueRegex = new Regex(NAME_VALUE_PATTERN);
private const string TAG_CMD = "*TAG";
/// <summary>
/// List of symbols. We keep them sorted by label because labels must be unique.
///
/// Idea: we could retain the end-of-line comments, and add them as comments in the
/// EQU section of the disassembly.
/// </summary>
private SortedList<string, Symbol> mSymbols =
new SortedList<string, Symbol>(Asm65.Label.LABEL_COMPARER);
@ -139,11 +139,27 @@ namespace SourceGen {
(1 << 24) - 1, out value);
badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS;
}
int width = -1;
string widthStr = matches[0].Groups[4].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;
}
} else {
badParseMsg =
CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT;
}
}
if (!parseOk) {
report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning,
badParseMsg);
} else {
string comment = matches[0].Groups[4].Value;
string comment = matches[0].Groups[5].Value;
if (comment.Length > 0) {
// remove ';'
comment = comment.Substring(1);
@ -152,7 +168,7 @@ namespace SourceGen {
FormatDescriptor.GetSubTypeForBase(numBase);
DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform,
isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr,
subType, comment, tag);
subType, comment, tag, width, width > 0);
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

View File

@ -119,7 +119,7 @@ namespace SourceGen {
}
public void DebugDump() {
Debug.WriteLine("Problem list:");
Debug.WriteLine("Problem list (" + mList.Count + " entries):");
foreach (ProblemEntry entry in mList) {
Debug.WriteLine(entry);
}

View File

@ -311,10 +311,13 @@ namespace SourceGen {
public class SerDefSymbol : SerSymbol {
public SerFormatDescriptor DataDescriptor { get; set; }
public string Comment { get; set; }
public bool HasWidth { 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;
}
}
@ -736,7 +739,7 @@ namespace SourceGen {
return false;
}
outDefSym = new DefSymbol(sym, dfd, serDefSym.Comment);
outDefSym = new DefSymbol(sym, dfd, serDefSym.HasWidth, serDefSym.Comment);
return true;
}
@ -837,13 +840,20 @@ namespace SourceGen {
outLvt = new LocalVariableTable();
outLvt.ClearPrevious = serTable.ClearPrevious;
foreach (SerDefSymbol serDef in serTable.Variables) {
// Force the "has width" field to true for local variables, because it's
// non-optional there. This is really only needed for loading projects
// created in v1.3, which didn't have the "has width" property.
serDef.HasWidth = true;
if (!CreateDefSymbol(serDef, contentVersion, report, out DefSymbol defSym)) {
return false;
}
if (!defSym.IsVariable) {
// not expected to happen
// not expected to happen; skip it
Debug.WriteLine("Found local variable with bad source: " +
defSym.SymbolSource);
string str = string.Format(Res.Strings.ERR_BAD_LOCAL_VARIABLE_FMT,
defSym);
report.Add(FileLoadItem.Type.Warning, str);
continue;
}
outLvt.AddOrReplace(defSym);

View File

@ -370,6 +370,43 @@ namespace SourceGen {
return po;
}
/// <summary>
/// Adds an additional annotation to an EQU directive, indicating whether the symbol
/// is a constant or an address, and (if address) how many bytes it spans.
/// </summary>
/// <param name="formatter">Formatter object.</param>
/// <param name="operand">Formatted operand string.</param>
/// <param name="defSym">Project/platform/variable symbol.</param>
/// <returns></returns>
public static string AnnotateEquDirective(Formatter formatter, string operand,
DefSymbol defSym) {
string typeStr;
if (defSym.SymbolType == Symbol.Type.Constant) {
if (defSym.SymbolSource == Symbol.Source.Variable) {
typeStr = Res.Strings.EQU_STACK_RELATIVE;
} else {
typeStr = Res.Strings.EQU_CONSTANT;
}
} else {
typeStr = Res.Strings.EQU_ADDRESS;
}
string msgStr = null;
if (defSym.HasWidth) {
msgStr = typeStr + "/" + defSym.DataDescriptor.Length;
} else if (defSym.SymbolType == Symbol.Type.Constant) {
// not entirely convinced we want this, but there's currently no other way
// to tell the difference between an address and a constant from the code list
msgStr = typeStr;
}
if (msgStr == null) {
return operand;
} else {
return operand + " {" + msgStr + "}";
}
}
/// <summary>
/// Converts a collection of bytes that represent a string into an array of formatted
/// string operands.

View File

@ -23,6 +23,7 @@ limitations under the License.
<system:String x:Key="str_AbbrevAddress">Addr</system:String>
<system:String x:Key="str_AbbrevConstant">Const</system:String>
<system:String x:Key="str_AbbrevStackRelative">StkRl</system:String>
<system:String x:Key="str_AsmLatestVersion">[latest version]</system:String>
<system:String x:Key="str_AsmMatchFailure">output DOES NOT match data file</system:String>
<system:String x:Key="str_AsmMatchSuccess">output matches data file</system:String>
@ -39,10 +40,14 @@ limitations under the License.
<system:String x:Key="str_DefaultHighAsciiDelimPat">&#x201c;#&#x201d;</system:String>
<system:String x:Key="str_DefaultC64PetsciiDelimPat">pet:&#x201c;#&#x201d;</system:String>
<system:String x:Key="str_DefaultC64ScreenCodeDelimPat">scr:&#x201c;#&#x201d;</system:String>
<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_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>
<system:String x:Key="str_ErrBadIdent">Invalid file identifier</system:String>
<system:String x:Key="str_ErrBadLocalVariableFmt">Bad local variable {0}</system:String>
<system:String x:Key="str_ErrBadLvTableFmt">Invalid local variable table at +{0:x6}</system:String>
<system:String x:Key="str_ErrBadRange">Bad range</system:String>
<system:String x:Key="str_ErrBadSymbolSt">Unknown Source or Type in symbol</system:String>

View File

@ -27,6 +27,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_AbbrevAddress");
public static string ABBREV_CONSTANT =
(string)Application.Current.FindResource("str_AbbrevConstant");
public static string ABBREV_STACK_RELATIVE =
(string)Application.Current.FindResource("str_AbbrevStackRelative");
public static string ASM_LATEST_VERSION =
(string)Application.Current.FindResource("str_AsmLatestVersion");
public static string ASM_MATCH_FAILURE =
@ -59,6 +61,12 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ClipformatAssemblerSource");
public static string CLIPFORMAT_DISASSEMBLY =
(string)Application.Current.FindResource("str_ClipformatDisassembly");
public static string EQU_ADDRESS =
(string)Application.Current.FindResource("str_EquAddress");
public static string EQU_CONSTANT =
(string)Application.Current.FindResource("str_EquConstant");
public static string EQU_STACK_RELATIVE =
(string)Application.Current.FindResource("str_EquStackRelative");
public static string ERR_BAD_FD_FMT =
(string)Application.Current.FindResource("str_ErrBadFdFmt");
public static string ERR_BAD_FD_FORMAT =
@ -67,6 +75,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrBadFileLength");
public static string ERR_BAD_IDENT =
(string)Application.Current.FindResource("str_ErrBadIdent");
public static string ERR_BAD_LOCAL_VARIABLE_FMT =
(string)Application.Current.FindResource("str_ErrBadLocalVariableFmt");
public static string ERR_BAD_LV_TABLE_FMT =
(string)Application.Current.FindResource("str_ErrBadLvTableFmt");
public static string ERR_BAD_RANGE =

View File

@ -3,10 +3,11 @@
;
; Sources:
; What's Where in the Apple, by William F. Luebbert
; S-C DocuMentor: Applesoft, by Bob Sander-Cederlof
*SYNOPSIS Applesoft BASIC addresses and constants
BAS_USRVEC @ $0A ;USR() command vector
BAS_USRVEC @ $0A 3 ;USR() command vector
BAS_CHARAC @ $0D ;used by string utility
BAS_ENDCHR @ $0E ;used by string utility
BAS_VALTYP @ $11 ;flag for last FAC operation ($00=num, $FF=str)
@ -16,41 +17,41 @@ BAS_COUNTH @ $1D ;hi-res high-order byte of step for line
BAS_HBASL @ $26 ;base address for hi-res drawing (lo part)
BAS_HBASH @ $27 ;base address for hi-res drawing (hi part)
BAS_HMASK @ $30 ;hi-res graphics on-the-fly bit mask
BAS_LINNUM @ $50 ;line number (2b)
BAS_TEMPPT @ $52 ;temporary point (2b)
BAS_INDEX @ $5E ;temp (stack) pointer for moving strings (2b)
BAS_TEXTTAB @ $67 ;pointer to start of Applesoft program (2b)
BAS_VARTAB @ $69 ;pointer to start of Applesoft variables (2b)
BAS_ARYTAB @ $6B ;pointer to start of Applesoft array space (2b)
BAS_STREND @ $6D ;pointer to end of numeric storage (2b)
BAS_FRETOP @ $6F ;pointer to end of string storage (2b)
BAS_MEMSIZE @ $73 ;HIMEM (2b)
BAS_CURLIN @ $75 ;current line number (2b)
BAS_OLDLIN @ $77 ;last line executed (2b)
BAS_LINNUM @ $50 2 ;line number (2b)
BAS_TEMPPT @ $52 2 ;temporary point (2b)
BAS_INDEX @ $5E 2 ;temp (stack) pointer for moving strings (2b)
BAS_TEXTTAB @ $67 2 ;pointer to start of Applesoft program (2b)
BAS_VARTAB @ $69 2 ;pointer to start of Applesoft variables (2b)
BAS_ARYTAB @ $6B 2 ;pointer to start of Applesoft array space (2b)
BAS_STREND @ $6D 2 ;pointer to end of numeric storage (2b)
BAS_FRETOP @ $6F 2 ;pointer to end of string storage (2b)
BAS_MEMSIZE @ $73 2 ;HIMEM (2b)
BAS_CURLIN @ $75 2 ;current line number (2b)
BAS_OLDLIN @ $77 2 ;last line executed (2b)
BAS_OLDTEXT @ $79 ;old text pointer
BAS_DATLIN @ $7B ;current line # from which data is being read (2b)
BAS_DATPTR @ $7D ;points to mem from which data is being read (2b)
BAS_VARNAM @ $81 ;holds last-used variable's name (2b)
BAS_VARPNT @ $83 ;pointer to last-used variable's value (2b)
BAS_FORPNT @ $85 ;general pointer (2b)
BAS_JMPADRS @ $90 ;jump address; $90 is set to $4C (3b)
BAS_DATLIN @ $7B 2 ;current line # from which data is being read (2b)
BAS_DATPTR @ $7D 2 ;points to mem from which data is being read (2b)
BAS_VARNAM @ $81 2 ;holds last-used variable's name (2b)
BAS_VARPNT @ $83 2 ;pointer to last-used variable's value (2b)
BAS_FORPNT @ $85 2 ;general pointer (2b)
BAS_JMPADRS @ $90 3 ;jump address; $90 is set to $4C (3b)
BAS_TEMP1 @ $93 ;fp math register
BAS_HIGHDS @ $94 ;block copy pointer (2b)
BAS_HIGHTR @ $96 ;block copy pointer (2b)
BAS_HIGHDS @ $94 2 ;block copy pointer (2b)
BAS_HIGHTR @ $96 2 ;block copy pointer (2b)
BAS_TEMP2 @ $98 ;fp math register
BAS_LOWTR @ $9B ;general pointer (2b)
BAS_FAC @ $9D ;floating point accumulator (6b)
BAS_LOWTR @ $9B 2 ;general pointer (2b)
BAS_FAC @ $9D 6 ;floating point accumulator (6b)
BAS_FACMO = $A0 ;middle-order byte of mantissa of FAC
BAS_FACLO = $A1 ;low-order byte of mantissa of FAC
BAS_FACSIGN @ $A2 ;single byte sign of FAC
BAS_ARG @ $A5 ;secondary floating point accumulator (6b)
BAS_STRNG1 @ $AB ;pointer to a string (2b)
BAS_STRNG2 @ $AD ;pointer to a string (2b)
BAS_PRGEND @ $AF ;pointer to end of program (2b)
BAS_ARG @ $A5 6 ;secondary floating point accumulator (6b)
BAS_STRNG1 @ $AB 2 ;pointer to a string (2b)
BAS_STRNG2 @ $AD 2 ;pointer to a string (2b)
BAS_PRGEND @ $AF 2 ;pointer to end of program (2b)
BAS_CHRGET @ $B1 ;get next character or Applesoft token
BAS_CHRGOT @ $B7 ;get next, but don't advance TXTPTR
BAS_TXTPTR @ $B8 ;points at next char or token
BAS_RND @ $B8 ;floating point random number (5b)
BAS_TXTPTR @ $B8 2 ;points at next char or token (2b)
BAS_RND @ $C9 5 ;floating point random number (5b)
BAS_AUTORUN @ $D6 ;set to $80 to auto-run
BAS_ERRFLG @ $D8 ;$80 if onerr active
BAS_HPAG @ $E6 ;hi-res page to draw on ($20 or $40)
@ -59,7 +60,7 @@ BAS_FIRST @ $F0 ;used for lo-res plot coordinates
BAS_ORMASK @ $F3 ;mask for output control
BAS_REMSTK @ $F8 ;stack pointer saved before each statement
BAS_AMPERV @ $03F5 ;JMP to function that handles Applesoft '&' cmds
BAS_AMPERV @ $03F5 3 ;JMP to function that handles Applesoft '&' cmds
;
; Useful Applesoft routines.

View File

@ -35,12 +35,12 @@ MON_RNDH @ $4F ;high byte of KEYIN "random" value
MON_SPDBYT @ $F1 ;text output speed limiter
MON_BRKV @ $03F0 ;address of BRK handler
MON_SOFTEVEC @ $03F2 ;address of RESET handler
MON_BRKV @ $03F0 2 ;address of BRK handler
MON_SOFTEVEC @ $03F2 2 ;address of RESET handler
MON_PWREDUP @ $03F4 ;power-up RESET checksum
MON_USRADDR @ $03F8 ;jump to function that handles monitor Ctrl-Y
MON_NMIVEC @ $03FB ;jump to function that handles NMI
MON_IRQADDR @ $03FE ;address of IRQ handler
MON_USRADDR @ $03F8 3 ;jump to function that handles monitor Ctrl-Y
MON_NMIVEC @ $03FB 3 ;jump to function that handles NMI
MON_IRQADDR @ $03FE 2 ;address of IRQ handler
MON_PLOT @ $F800 ;lo-res plot at X=Y-reg, Y=Acc
MON_PLOT1 @ $F80E ;lo-res plot at X=Y-reg, Y per GBASL/H
@ -101,7 +101,7 @@ MON_CLREOP @ $FC42 ;clear screen from cursor to end of page
MON_HOME @ $FC58 ;clear screen and reset text output to top-left
MON_CR @ $FC62 ;perform a carriage return
MON_LF @ $FC66 ;perform a line feed
MON_SCROLL @ $FC70 ;scroll up 1 line
MON_SCROLL @ $FC70 ;scroll up one line
MON_CLREOL @ $FC9C ;clear to end of line
MON_CLREOLZ @ $FC9E
MON_WAIT @ $FCA8 ;delay for (26 + 27*Acc + 5*(Acc*Acc))/2 cycles

View File

@ -104,9 +104,9 @@ namespace SourceGen {
/// Constructs immutable object.
/// </summary>
/// <param name="label">Label string. Syntax assumed valid.</param>
/// <param name="source">User-defined or auto-generated?</param>
/// <param name="value">Symbol value.</param>
/// <param name="source">User-defined, auto-generated, ?</param>
/// <param name="type">Type of symbol this is.</param>
/// user-defined.</param>
public Symbol(string label, int value, Source source, Type type) {
Debug.Assert(!string.IsNullOrEmpty(label));
Label = label;

View File

@ -19,13 +19,21 @@ limitations under the License.
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Edit Symbol"
SizeToContent="Height" Width="320" ResizeMode="NoResize"
SizeToContent="Height" Width="340" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded"
ContentRendered="Window_ContentRendered">
<Window.Resources>
<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>
</Window.Resources>
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@ -41,42 +49,44 @@ limitations under the License.
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Label:"/>
<StackPanel Grid.Column="1" Grid.Row="0">
<StackPanel Grid.Column="1" Grid.Row="0" 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,16"/>
<TextBlock Name="labelUniqueLabel" Text="• Unique" Margin="0,4,0,16"/>
<TextBlock Name="projectLabelUniqueLabel" Text="• Unique among project symbols" Margin="0,4,0,0"/>
<TextBlock Name="labelUniqueLabel" Text="• Unique" Margin="0,4,0,0"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Value:"/>
<StackPanel Grid.Column="1" Grid.Row="1">
<StackPanel Grid.Column="1" Grid.Row="1" 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="valueRangeLabel" Text="• Value between 0-255, including width" Margin="0,4,0,0"/>
<TextBlock Name="valueUniqueLabel" Text="• Values in table must not overlap" Margin="0,4,0,0"/>
<TextBlock Name="valueNotesLabel" Text="• Decimal, hex ($), or binary (%)" Margin="0,4,0,16"/>
<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="valueNotesLabel" Text="• Decimal, hex ($), or binary (%)" Margin="0,4,0,0"/>
</StackPanel>
<TextBlock Name="widthEntry1" Grid.Column="0" Grid.Row="2" Text="Width:"/>
<StackPanel Name="widthEntry2" Grid.Column="1" Grid.Row="2">
<TextBlock Grid.Column="0" Grid.Row="2" Text="Width:"/>
<StackPanel Grid.Column="1" Grid.Row="2" Margin="0,0,0,16">
<TextBox Margin="0,1,0,0" Text="{Binding VarWidth, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Name="widthNotesLabel" Text="• Decimal value, 1-8" Margin="0,4,0,16"/>
<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"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Comment:" Margin="0,0,8,0"/>
<StackPanel Grid.Column="1" Grid.Row="3">
<StackPanel Grid.Column="1" Grid.Row="3" 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,16"/>
<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="Constant" Margin="24,0,0,0" IsChecked="{Binding IsConstant}"/>
<RadioButton Content="{Binding ConstantLabel, FallbackValue=Constant}" Margin="24,0,0,0" IsChecked="{Binding IsConstant}"/>
</StackPanel>
</GroupBox>

View File

@ -61,6 +61,12 @@ namespace SourceGen.WpfGui {
}
private string mWidth;
public string WidthLimitLabel {
get { return mWidthLimitLabel; }
set { mWidthLimitLabel = value; OnPropertyChanged(); }
}
private string mWidthLimitLabel;
public string Comment {
get { return mComment; }
set { mComment = value; OnPropertyChanged(); }
@ -79,6 +85,12 @@ namespace SourceGen.WpfGui {
}
private bool mIsConstant;
public string ConstantLabel {
get { return mConstantLabel; }
set { mConstantLabel = value; OnPropertyChanged(); }
}
private string mConstantLabel;
public bool ReadOnlyValueAndType {
get { return mReadOnlyValueAndType; }
set { mReadOnlyValueAndType = value; OnPropertyChanged(); }
@ -113,6 +125,11 @@ namespace SourceGen.WpfGui {
/// </summary>
private bool mIsVariable;
/// <summary>
/// Set to true if the width value is optional.
/// </summary>
private bool mIsWidthOptional;
// Saved off at dialog load time.
private Brush mDefaultLabelColor;
@ -135,6 +152,9 @@ namespace SourceGen.WpfGui {
/// Constructor, for editing a local variable, or editing a project symbol with
/// the value field locked.
/// </summary>
/// <remarks>
/// TODO(someday): disable the "constant" radio button unless CPU=65816.
/// </remarks>
public EditDefSymbol(Window owner, Formatter formatter,
SortedList<string, DefSymbol> defList, DefSymbol defSym,
SymbolTable symbolTable, bool isVariable, bool lockValueAndType) {
@ -150,16 +170,24 @@ namespace SourceGen.WpfGui {
mReadOnlyValueAndType = lockValueAndType;
Label = Value = VarWidth = Comment = string.Empty;
// hide the inappropriate stuff
int maxWidth;
if (isVariable) {
widthEntry1.Visibility = widthEntry2.Visibility = labelUniqueLabel.Visibility =
Visibility.Visible;
projectLabelUniqueLabel.Visibility = Visibility.Collapsed;
} else {
widthEntry1.Visibility = widthEntry2.Visibility = labelUniqueLabel.Visibility =
projectLabelUniqueLabel.Visibility = widthOptionalLabel.Visibility =
Visibility.Collapsed;
labelUniqueLabel.Visibility = Visibility.Collapsed;
valueRangeLabel.Visibility = valueUniqueLabel.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;
}
mIsWidthOptional = !isVariable;
string fmt = (string)FindResource("str_WidthLimitFmt");
WidthLimitLabel = string.Format(fmt, maxWidth);
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
@ -169,7 +197,9 @@ namespace SourceGen.WpfGui {
Label = mOldSym.Label;
Value = mNumFormatter.FormatValueInBase(mOldSym.Value,
mOldSym.DataDescriptor.NumBase);
VarWidth = mOldSym.DataDescriptor.Length.ToString();
if (mOldSym.HasWidth) {
VarWidth = mOldSym.DataDescriptor.Length.ToString();
}
Comment = mOldSym.Comment;
if (mOldSym.SymbolType == Symbol.Type.Constant) {
@ -190,6 +220,9 @@ namespace SourceGen.WpfGui {
labelTextBox.Focus();
}
/// <summary>
/// Validates input and updates controls appropriately.
/// </summary>
private void UpdateControls() {
if (!IsLoaded) {
return;
@ -227,12 +260,19 @@ namespace SourceGen.WpfGui {
//}
bool widthValid = true;
int thisWidth = 0;
if (widthEntry1.Visibility == Visibility.Visible) {
if (!int.TryParse(VarWidth, out thisWidth) ||
thisWidth < DefSymbol.MIN_WIDTH || thisWidth > DefSymbol.MAX_WIDTH) {
widthValid = false;
}
int thisWidth = -1;
if (IsConstant && !mIsVariable) {
// 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)) {
// 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.
widthValid = false;
}
bool valueRangeValid = true;
@ -262,8 +302,8 @@ namespace SourceGen.WpfGui {
labelUniqueLabel.Foreground = projectLabelUniqueLabel.Foreground =
labelUnique ? mDefaultLabelColor : Brushes.Red;
valueNotesLabel.Foreground = valueValid ? mDefaultLabelColor : Brushes.Red;
valueRangeLabel.Foreground = valueRangeValid ? mDefaultLabelColor : Brushes.Red;
valueUniqueLabel.Foreground = valueUniqueValid ? mDefaultLabelColor : Brushes.Red;
varValueRangeLabel.Foreground = valueRangeValid ? mDefaultLabelColor : Brushes.Red;
varValueUniqueLabel.Foreground = valueUniqueValid ? mDefaultLabelColor : Brushes.Red;
widthNotesLabel.Foreground = widthValid ? mDefaultLabelColor : Brushes.Red;
IsValid = labelValid && labelUnique && valueValid && valueRangeValid &&
@ -284,15 +324,18 @@ namespace SourceGen.WpfGui {
private void OkButton_Click(object sender, RoutedEventArgs e) {
ParseValue(out int value, out int numBase);
FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase);
int width = DefSymbol.NO_WIDTH;
if (!string.IsNullOrEmpty(VarWidth)) {
width = int.Parse(VarWidth);
int width = -1;
if (IsConstant && !mIsVariable) {
// 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);
}
NewSym = new DefSymbol(Label, value,
mIsVariable ? Symbol.Source.Variable : Symbol.Source.Project,
IsConstant ? Symbol.Type.Constant : Symbol.Type.ExternalAddr,
subType, Comment, string.Empty, width);
subType, Comment, string.Empty, width, width > 0);
DialogResult = true;
}

View File

@ -961,8 +961,7 @@ namespace SourceGen.WpfGui {
symName = SymbolLabel; // may not be valid, but it doesn't have to be
}
origSym = new DefSymbol(symName, mOperandValue, Symbol.Source.Project,
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None,
string.Empty, string.Empty);
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None);
}
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,
@ -1147,7 +1146,7 @@ namespace SourceGen.WpfGui {
// dialog will handle it.
initialVar = new DefSymbol("VAR", mOperandValue,
Symbol.Source.Variable, symType, FormatDescriptor.SubType.None,
string.Empty, string.Empty, 1);
string.Empty, string.Empty, 1, true);
}
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,

View File

@ -161,7 +161,7 @@ namespace SourceGen.WpfGui {
DefSymbol defSym = mWorkTable[i];
string typeStr;
if (defSym.SymbolType == Symbol.Type.Constant) {
typeStr = Res.Strings.ABBREV_CONSTANT;
typeStr = Res.Strings.ABBREV_STACK_RELATIVE;
} else {
typeStr = Res.Strings.ABBREV_ADDRESS;
}

View File

@ -538,14 +538,13 @@ namespace SourceGen.WpfGui {
}
// Import all user labels that were marked as "global export". These become
// external-address project symbols.
// external-address project symbols with unspecified width.
int foundCount = 0;
foreach (KeyValuePair<int, Symbol> kvp in newProject.UserLabels) {
if (kvp.Value.SymbolType == Symbol.Type.GlobalAddrExport) {
Symbol sym = kvp.Value;
DefSymbol defSym = new DefSymbol(sym.Label, sym.Value, Symbol.Source.Project,
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None,
string.Empty, string.Empty);
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None);
mWorkProps.ProjectSyms[defSym.Label] = defSym;
foundCount++;
}