From 2a41d70e04078158a54fcb3a58ee812174f96138 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Tue, 1 Oct 2019 14:58:24 -0700 Subject: [PATCH] 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. --- Asm65/Number.cs | 3 +- SourceGen/DefSymbol.cs | 70 ++++++++++++---- SourceGen/FormatDescriptor.cs | 2 +- SourceGen/LineListGen.cs | 2 + SourceGen/PlatformSymbols.cs | 34 ++++++-- SourceGen/ProblemList.cs | 2 +- SourceGen/ProjectFile.cs | 14 +++- SourceGen/PseudoOp.cs | 37 +++++++++ SourceGen/Res/Strings.xaml | 5 ++ SourceGen/Res/Strings.xaml.cs | 10 +++ SourceGen/RuntimeData/Apple/Applesoft.sym65 | 59 ++++++------- SourceGen/RuntimeData/Apple/F8-ROM.sym65 | 12 +-- SourceGen/Symbol.cs | 4 +- SourceGen/WpfGui/EditDefSymbol.xaml | 38 +++++---- SourceGen/WpfGui/EditDefSymbol.xaml.cs | 83 ++++++++++++++----- .../WpfGui/EditInstructionOperand.xaml.cs | 5 +- .../WpfGui/EditLocalVariableTable.xaml.cs | 2 +- .../WpfGui/EditProjectProperties.xaml.cs | 5 +- 18 files changed, 278 insertions(+), 109 deletions(-) diff --git a/Asm65/Number.cs b/Asm65/Number.cs index ae97999..50261c3 100644 --- a/Asm65/Number.cs +++ b/Asm65/Number.cs @@ -19,7 +19,8 @@ using System.Diagnostics; namespace Asm65 { public class Number { /// - /// 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. /// diff --git a/SourceGen/DefSymbol.cs b/SourceGen/DefSymbol.cs index 8bbddd2..c172bfb 100644 --- a/SourceGen/DefSymbol.cs +++ b/SourceGen/DefSymbol.cs @@ -18,25 +18,39 @@ using System.Diagnostics; namespace SourceGen { /// - /// 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. /// /// - /// 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. /// 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; /// /// Data format descriptor. /// public FormatDescriptor DataDescriptor { get; private set; } + /// + /// True if a width was specified for this symbol. + /// + /// + /// 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. + /// + public bool HasWidth { get; private set; } + /// /// User-supplied comment. /// @@ -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 /// @@ -79,7 +95,7 @@ namespace SourceGen { } /// - /// Constructor. + /// Constructor. Limited form, used in a couple of places. /// /// Symbol's label. /// Symbol's value. @@ -87,14 +103,13 @@ namespace SourceGen { /// Symbol type. /// Format descriptor sub-type, so we know how the /// user wants the value to be displayed. - /// End-of-line comment. - /// Symbol tag, used for grouping platform symbols. 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) { } /// - /// Constructor. Used for local variables, which have a meaningful width. + /// Constructor. General form. /// /// Symbol's label. /// Symbol's value. @@ -105,31 +120,52 @@ namespace SourceGen { /// End-of-line comment. /// Symbol tag, used for grouping platform symbols. /// Variable width. + /// True if width was explicitly specified. If this is + /// false, the value of the "width" argument is ignored. 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; } /// - /// 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. /// /// Base symbol. /// Format descriptor. + /// Set if a width was explicitly specified. /// End-of-line comment. - 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) { } /// /// Determines whether a symbol overlaps with a region. Useful for variables. diff --git a/SourceGen/FormatDescriptor.cs b/SourceGen/FormatDescriptor.cs index 2dbc227..7b0cdb2 100644 --- a/SourceGen/FormatDescriptor.cs +++ b/SourceGen/FormatDescriptor.cs @@ -162,7 +162,7 @@ namespace SourceGen { /// /// Constructor for base type data item. /// - /// Length, in bytes. + /// Length, in bytes. /// Format type. /// Format sub-type. private FormatDescriptor(int length, Type fmt, SubType subFmt) { diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index 95817cb..bb4c0d9 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -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), diff --git a/SourceGen/PlatformSymbols.cs b/SourceGen/PlatformSymbols.cs index 109e1af..f6a2469 100644 --- a/SourceGen/PlatformSymbols.cs +++ b/SourceGen/PlatformSymbols.cs @@ -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). /// + /// + /// If you want to make sense of this, I highly recommend https://regex101.com/ . + /// 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"; /// /// 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. /// private SortedList mSymbols = new SortedList(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 diff --git a/SourceGen/ProblemList.cs b/SourceGen/ProblemList.cs index 81b9404..f29f9f7 100644 --- a/SourceGen/ProblemList.cs +++ b/SourceGen/ProblemList.cs @@ -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); } diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index 6230f2c..8f2590c 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -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); diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 2edd3f7..82d1c83 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -370,6 +370,43 @@ namespace SourceGen { return po; } + /// + /// 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. + /// + /// Formatter object. + /// Formatted operand string. + /// Project/platform/variable symbol. + /// + 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 + "}"; + } + } + /// /// Converts a collection of bytes that represent a string into an array of formatted /// string operands. diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index 5e6198c..ce1ac4a 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -23,6 +23,7 @@ limitations under the License. Addr Const + StkRl [latest version] output DOES NOT match data file output matches data file @@ -39,10 +40,14 @@ limitations under the License. “#” pet:“#” scr:“#” + addr + const + stkrl Bad format descriptor at +{0:x6}. Bad format descriptor type Bad file length Invalid file identifier + Bad local variable {0} Invalid local variable table at +{0:x6} Bad range Unknown Source or Type in symbol diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs index 8213616..9fd40c4 100644 --- a/SourceGen/Res/Strings.xaml.cs +++ b/SourceGen/Res/Strings.xaml.cs @@ -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 = diff --git a/SourceGen/RuntimeData/Apple/Applesoft.sym65 b/SourceGen/RuntimeData/Apple/Applesoft.sym65 index 9d48cf9..0559110 100644 --- a/SourceGen/RuntimeData/Apple/Applesoft.sym65 +++ b/SourceGen/RuntimeData/Apple/Applesoft.sym65 @@ -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. diff --git a/SourceGen/RuntimeData/Apple/F8-ROM.sym65 b/SourceGen/RuntimeData/Apple/F8-ROM.sym65 index 21c6b4e..cd31c37 100644 --- a/SourceGen/RuntimeData/Apple/F8-ROM.sym65 +++ b/SourceGen/RuntimeData/Apple/F8-ROM.sym65 @@ -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 diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index a4315e2..26737e5 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -104,9 +104,9 @@ namespace SourceGen { /// Constructs immutable object. /// /// Label string. Syntax assumed valid. - /// User-defined or auto-generated? + /// Symbol value. + /// User-defined, auto-generated, ? /// Type of symbol this is. - /// user-defined. public Symbol(string label, int value, Source source, Type type) { Debug.Assert(!string.IsNullOrEmpty(label)); Label = label; diff --git a/SourceGen/WpfGui/EditDefSymbol.xaml b/SourceGen/WpfGui/EditDefSymbol.xaml index 0d5b061..4acf0b3 100644 --- a/SourceGen/WpfGui/EditDefSymbol.xaml +++ b/SourceGen/WpfGui/EditDefSymbol.xaml @@ -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"> + + + • Decimal or hex value, 1-{0} + Constant + Stack-Relative Offset + + @@ -41,42 +49,44 @@ limitations under the License. - + - - + + - + - - - + + + - - + + - + + - + - + - + diff --git a/SourceGen/WpfGui/EditDefSymbol.xaml.cs b/SourceGen/WpfGui/EditDefSymbol.xaml.cs index eee86f4..3ae7a4e 100644 --- a/SourceGen/WpfGui/EditDefSymbol.xaml.cs +++ b/SourceGen/WpfGui/EditDefSymbol.xaml.cs @@ -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 { /// private bool mIsVariable; + /// + /// Set to true if the width value is optional. + /// + 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. /// + /// + /// TODO(someday): disable the "constant" radio button unless CPU=65816. + /// public EditDefSymbol(Window owner, Formatter formatter, SortedList 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(); } + /// + /// Validates input and updates controls appropriately. + /// 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; } diff --git a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs index 44bcd4a..f92f39c 100644 --- a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs +++ b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs @@ -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, diff --git a/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs b/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs index 0ab29fd..1e37638 100644 --- a/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs +++ b/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs @@ -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; } diff --git a/SourceGen/WpfGui/EditProjectProperties.xaml.cs b/SourceGen/WpfGui/EditProjectProperties.xaml.cs index cebc6d8..81ec225 100644 --- a/SourceGen/WpfGui/EditProjectProperties.xaml.cs +++ b/SourceGen/WpfGui/EditProjectProperties.xaml.cs @@ -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 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++; }