diff --git a/Asm65/CharEncoding.cs b/Asm65/CharEncoding.cs index 76134e1..4851cdb 100644 --- a/Asm65/CharEncoding.cs +++ b/Asm65/CharEncoding.cs @@ -193,17 +193,29 @@ namespace Asm65 { public static bool IsExtendedC64Petscii(byte val) { return sExtendedPetscii[val]; } - public static char ConvertC64Petscii(byte val) { - if ((val >= 0x20 && val <= 0x40) || val == 0x5b || val == 0x5d) { - return (char)val; // number/symbols, '[', ']' - } else if (val >= 0x41 && val <= 0x5a) { - return (char)(val + 0x20); // lower case - } else if (val >= 0xc1 && val <= 0xda) { - return (char)(val - 0x80); // upper case - } else { - Debug.Assert(!IsPrintableC64Petscii(val)); - return UNPRINTABLE_CHAR; + private static char[] sPetsciiToUnicode = CreatePetsciiToUnicodeMap(); + private static char[] CreatePetsciiToUnicodeMap() { + // There are performance arguments for doing this with and without a table. For + // x64 with fast memory and large caches, table seems reasonable. + char[] map = new char[256]; + for (int val = 0; val < 256; val++) { + char ch; + if ((val >= 0x20 && val <= 0x40) || val == 0x5b || val == 0x5d) { + ch = (char)val; // number/symbols, '[', ']' + } else if (val >= 0x41 && val <= 0x5a) { + ch = (char)(val + 0x20); // lower case + } else if (val >= 0xc1 && val <= 0xda) { + ch = (char)(val - 0x80); // upper case + } else { + Debug.Assert(!IsPrintableC64Petscii((byte)val)); + ch = UNPRINTABLE_CHAR; + } + map[val] = ch; } + return map; + } + public static char ConvertC64Petscii(byte val) { + return sPetsciiToUnicode[val]; } // @@ -243,19 +255,29 @@ namespace Asm65 { public static bool IsExtendedC64ScreenCode(byte val) { return sPrintableScreenCode[val]; } - public static char ConvertC64ScreenCode(byte val) { - if (val == 0x00 || val == 0x1b || val == 0x1d) { - return (char)(val + 0x40); // '@', '[', ']' - } else if (val >= 0x01 && val <= 0x1a) { - return (char)(val + 0x60); // lower case - } else if (val >= 0x20 && val <= 0x3f) { - return (char)(val); // numbers/symbols - } else if (val >= 0x41 && val <= 0x5a) { - return (char)(val); // upper case - } else { - Debug.Assert(!IsPrintableC64ScreenCode(val)); - return UNPRINTABLE_CHAR; + private static char[] sScreenCodeToUnicode = CreateScreenCodeToUnicodeMap(); + private static char[] CreateScreenCodeToUnicodeMap() { + char[] map = new char[256]; + for (int val = 0; val < 256; val++) { + char ch; + if (val == 0x00 || val == 0x1b || val == 0x1d) { + ch = (char)(val + 0x40); // '@', '[', ']' + } else if (val >= 0x01 && val <= 0x1a) { + ch = (char)(val + 0x60); // lower case + } else if (val >= 0x20 && val <= 0x3f) { + ch = (char)(val); // numbers/symbols + } else if (val >= 0x41 && val <= 0x5a) { + ch = (char)(val); // upper case + } else { + Debug.Assert(!IsPrintableC64ScreenCode((byte)val)); + ch = UNPRINTABLE_CHAR; + } + map[val] = ch; } + return map; + } + public static char ConvertC64ScreenCode(byte val) { + return sScreenCodeToUnicode[val]; } public static char ConvertLowAndHighC64ScreenCode(byte val) { return ConvertC64ScreenCode((byte)(val & 0x7f)); diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index 01de707..393fb3b 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -103,32 +103,33 @@ namespace SourceGen.AsmGen { // Pseudo-op string constants. - private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() { - EquDirective = "=", - OrgDirective = "!pseudopc", - //RegWidthDirective // !al, !as, !rl, !rs - DefineData1 = "!byte", - DefineData2 = "!word", - DefineData3 = "!24", - DefineData4 = "!32", - //DefineBigData2 - //DefineBigData3 - //DefineBigData4 - Fill = "!fill", - Dense = "!hex", - StrGeneric = "!text", // can use !xor for high ASCII - //StrReverse - //StrNullTerm - //StrLen8 - //StrLen16 - //StrDci - }; + private static PseudoOp.PseudoOpNames sDataOpNames = + new PseudoOp.PseudoOpNames(new Dictionary { + { "EquDirective", "=" }, + { "OrgDirective", "!pseudopc" }, + //RegWidthDirective // !al, !as, !rl, !rs + { "DefineData1", "!byte" }, + { "DefineData2", "!word" }, + { "DefineData3", "!24" }, + { "DefineData4", "!32" }, + //DefineBigData2 + //DefineBigData3 + //DefineBigData4 + { "Fill", "!fill" }, + { "Dense", "!hex" }, + { "StrGeneric", "!text" }, // can use !xor for high ASCII + //StrReverse + //StrNullTerm + //StrLen8 + //StrLen16 + //StrDci + }); // IGenerator public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps, out Formatter.FormatConfig formatConfig) { - pseudoOps = sDataOpNames.GetCopy(); + pseudoOps = sDataOpNames; formatConfig = new Formatter.FormatConfig(); SetFormatConfigValues(ref formatConfig); @@ -374,7 +375,7 @@ namespace SourceGen.AsmGen { break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); - if (opcodeStr == null) { + if (string.IsNullOrEmpty(opcodeStr)) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index c6bb871..e8fe0ce 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -100,32 +100,33 @@ namespace SourceGen.AsmGen { // Pseudo-op string constants. - private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() { - EquDirective = "=", - OrgDirective = ".org", - //RegWidthDirective // .a8, .a16, .i8, .i16 - DefineData1 = ".byte", - DefineData2 = ".word", - DefineData3 = ".faraddr", - DefineData4 = ".dword", - DefineBigData2 = ".dbyt", - //DefineBigData3 - //DefineBigData4 - Fill = ".res", - //Dense // no equivalent, use .byte with comma-separated args - StrGeneric = ".byte", - //StrReverse - StrNullTerm = ".asciiz", - //StrLen8 // macro with .strlen? - //StrLen16 - //StrDci - }; + private static PseudoOp.PseudoOpNames sDataOpNames = + new PseudoOp.PseudoOpNames(new Dictionary { + { "EquDirective", "=" }, + { "OrgDirective", ".org" }, + //RegWidthDirective // .a8, .a16, .i8, .i16 + { "DefineData1", ".byte" }, + { "DefineData2", ".word" }, + { "DefineData3", ".faraddr" }, + { "DefineData4", ".dword" }, + { "DefineBigData2", ".dbyt" }, + //DefineBigData3 + //DefineBigData4 + { "Fill", ".res" }, + //Dense // no equivalent, use .byte with comma-separated args + { "StrGeneric", ".byte" }, + //StrReverse + { "StrNullTerm", ".asciiz" }, + //StrLen8 // macro with .strlen? + //StrLen16 + //StrDci + }); // IGenerator public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps, out Formatter.FormatConfig formatConfig) { - pseudoOps = sDataOpNames.GetCopy(); + pseudoOps = sDataOpNames; formatConfig = new Formatter.FormatConfig(); SetFormatConfigValues(ref formatConfig); @@ -401,7 +402,7 @@ namespace SourceGen.AsmGen { break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); - if (opcodeStr == null) { + if ((string.IsNullOrEmpty(opcodeStr))) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 5d21765..d0a8300 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -90,37 +90,35 @@ namespace SourceGen.AsmGen { private CommonUtil.Version mAsmVersion = CommonUtil.Version.NO_VERSION; - // Semi-convenient way to hold all the interesting string constants in one place. - // Note the actual usage of the pseudo-op may not match what the main app does, - // e.g. RegWidthDirective behaves differently from "mx". I'm just trying to avoid - // having string constants scattered all over. - private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() { - EquDirective = "equ", - OrgDirective = "org", - RegWidthDirective = "mx", - DefineData1 = "dfb", - DefineData2 = "dw", - DefineData3 = "adr", - DefineData4 = "adrl", - DefineBigData2 = "ddb", - //DefineBigData3 - //DefineBigData4 - Fill = "ds", - Dense = "hex", - StrGeneric = "asc", - StrReverse = "rev", - //StrNullTerm - StrLen8 = "str", - StrLen16 = "strl", - StrDci = "dci", - }; + // Pseudo-op string constants. + private static PseudoOp.PseudoOpNames sDataOpNames = + new PseudoOp.PseudoOpNames(new Dictionary { + { "EquDirective", "equ" }, + { "OrgDirective", "org" }, + //RegWidthDirective + { "DefineData1", "dfb" }, + { "DefineData2", "dw" }, + { "DefineData3", "adr" }, + { "DefineData4", "adrl" }, + { "DefineBigData2", "ddb" }, + //DefineBigData3 + //DefineBigData4 + { "Fill", "ds" }, + { "Dense", "hex" }, + { "StrGeneric", "asc" }, + { "StrReverse", "rev" }, + //StrNullTerm + { "StrLen8", "str" }, + { "StrLen16", "strl" }, + { "StrDci", "dci" }, + }); + private const string REG_WIDTH_DIRECTIVE = "mx"; // IGenerator public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps, out Formatter.FormatConfig formatConfig) { - pseudoOps = sDataOpNames.GetCopy(); - pseudoOps.RegWidthDirective = string.Empty; + pseudoOps = sDataOpNames; formatConfig = new Formatter.FormatConfig(); SetFormatConfigValues(ref formatConfig); @@ -249,7 +247,7 @@ namespace SourceGen.AsmGen { break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); - if (opcodeStr == null) { + if ((string.IsNullOrEmpty(opcodeStr))) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { @@ -413,8 +411,7 @@ namespace SourceGen.AsmGen { // Assembler defaults to short regs, so we can skip this. return; } - OutputLine(string.Empty, - SourceFormatter.FormatPseudoOp(sDataOpNames.RegWidthDirective), + OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(REG_WIDTH_DIRECTIVE), "%" + newM + newX, string.Empty); } diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 7c1fa3d..395b97c 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -111,33 +111,34 @@ namespace SourceGen.AsmGen { // Pseudo-op string constants. - private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() { - EquDirective = "=", - OrgDirective = ".logical", - //RegWidthDirective // .as, .al, .xs, .xl - DefineData1 = ".byte", - DefineData2 = ".word", - DefineData3 = ".long", - DefineData4 = ".dword", - //DefineBigData2 - //DefineBigData3 - //DefineBigData4 - Fill = ".fill", - //Dense // no equivalent, use .byte with comma-separated args - StrGeneric = ".text", - //StrReverse - StrNullTerm = ".null", - StrLen8 = ".ptext", - //StrLen16 - StrDci = ".shift" - }; + private static PseudoOp.PseudoOpNames sDataOpNames = + new PseudoOp.PseudoOpNames(new Dictionary { + { "EquDirective", "=" }, + { "OrgDirective", ".logical" }, + //RegWidthDirective // .as, .al, .xs, .xl + { "DefineData1", ".byte" }, + { "DefineData2", ".word" }, + { "DefineData3", ".long" }, + { "DefineData4", ".dword" }, + //DefineBigData2 + //DefineBigData3 + //DefineBigData4 + { "Fill", ".fill" }, + //Dense // no equivalent, use .byte with comma-separated args + { "StrGeneric", ".text" }, + //StrReverse + { "StrNullTerm", ".null" }, + { "StrLen8", ".ptext" }, + //StrLen16 + { "StrDci", ".shift" } + }); private const string HERE_PSEUDO_OP = ".here"; // IGenerator public void GetDefaultDisplayFormat(out PseudoOp.PseudoOpNames pseudoOps, out Formatter.FormatConfig formatConfig) { - pseudoOps = sDataOpNames.GetCopy(); + pseudoOps = sDataOpNames; formatConfig = new Formatter.FormatConfig(); SetFormatConfigValues(ref formatConfig); @@ -165,7 +166,7 @@ namespace SourceGen.AsmGen { } /// - /// Configures the assembler-specific format items. + /// Configures the assembler-specific format items. May be called without a Project. /// private void SetFormatConfigValues(ref Formatter.FormatConfig config) { // Must be lower case when --case-sensitive is used. @@ -187,27 +188,6 @@ namespace SourceGen.AsmGen { config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Common; - - // Configure delimiters for single-character operands. The conversion mode we - // use is determined by the default text mode in the project properties. - Formatter.DelimiterSet charSet = new Formatter.DelimiterSet(); - TextScanMode textMode = Project.ProjectProps.AnalysisParams.DefaultTextScanMode; - switch (textMode) { - case TextScanMode.C64Petscii: - charSet.Set(CharEncoding.Encoding.C64Petscii, Formatter.SINGLE_QUOTE_DELIM); - break; - case TextScanMode.C64ScreenCode: - charSet.Set(CharEncoding.Encoding.C64ScreenCode, Formatter.SINGLE_QUOTE_DELIM); - break; - case TextScanMode.LowAscii: - case TextScanMode.LowHighAscii: - default: - charSet.Set(CharEncoding.Encoding.Ascii, Formatter.SINGLE_QUOTE_DELIM); - charSet.Set(CharEncoding.Encoding.HighAscii, - new Formatter.DelimiterDef(string.Empty, '\'', '\'', " | $80")); - break; - } - config.mCharDelimiters = charSet; } // IGenerator @@ -221,6 +201,29 @@ namespace SourceGen.AsmGen { Formatter.FormatConfig config = new Formatter.FormatConfig(); GenCommon.ConfigureFormatterFromSettings(Settings, ref config); SetFormatConfigValues(ref config); + + // Configure delimiters for single-character operands. The conversion mode we + // use is determined by the default text mode in the project properties. + Formatter.DelimiterSet charSet = new Formatter.DelimiterSet(); + TextScanMode textMode = Project.ProjectProps.AnalysisParams.DefaultTextScanMode; + switch (textMode) { + case TextScanMode.C64Petscii: + charSet.Set(CharEncoding.Encoding.C64Petscii, + Formatter.SINGLE_QUOTE_DELIM); + break; + case TextScanMode.C64ScreenCode: + charSet.Set(CharEncoding.Encoding.C64ScreenCode, + Formatter.SINGLE_QUOTE_DELIM); + break; + case TextScanMode.LowAscii: + case TextScanMode.LowHighAscii: + default: + charSet.Set(CharEncoding.Encoding.Ascii, Formatter.SINGLE_QUOTE_DELIM); + charSet.Set(CharEncoding.Encoding.HighAscii, + new Formatter.DelimiterDef(string.Empty, '\'', '\'', " | $80")); + break; + } + config.mCharDelimiters = charSet; SourceFormatter = new Formatter(config); string msg = string.Format(Res.Strings.PROGRESS_GENERATING_FMT, pathName); @@ -387,7 +390,7 @@ namespace SourceGen.AsmGen { break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); - if (opcodeStr == null) { + if ((string.IsNullOrEmpty(opcodeStr))) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { diff --git a/SourceGen/AsmGen/IGenerator.cs b/SourceGen/AsmGen/IGenerator.cs index 083f8b6..5172b36 100644 --- a/SourceGen/AsmGen/IGenerator.cs +++ b/SourceGen/AsmGen/IGenerator.cs @@ -28,8 +28,9 @@ namespace SourceGen.AsmGen { /// Returns some strings and format options for use in for the display list, configurable /// through the app settings "quick set" feature. These are not used when generating /// source code. - /// - /// This may be called on an unconfigured IGenerator. + /// + /// This may be called on an unconfigured IGenerator, so this should not expect to + /// have access to project properties. /// /// Table of pseudo-op names. /// Format configuration. diff --git a/SourceGen/DisplayList.cs b/SourceGen/DisplayList.cs index 56ce2d8..c161ff0 100644 --- a/SourceGen/DisplayList.cs +++ b/SourceGen/DisplayList.cs @@ -50,6 +50,7 @@ namespace SourceGen { /// https://www.codeproject.com/Articles/34405/WPF-Data-Virtualization?msg=5635751 /// https://web.archive.org/web/20121216034305/http://www.zagstudio.com/blog/498 /// https://web.archive.org/web/20121107200359/http://www.zagstudio.com/blog/378 + /// https://github.com/lvaleriu/Virtualization /// public class DisplayList : IList, IList, INotifyCollectionChanged, INotifyPropertyChanged { diff --git a/SourceGen/FormatDescriptor.cs b/SourceGen/FormatDescriptor.cs index 55c6ee2..6e9ce28 100644 --- a/SourceGen/FormatDescriptor.cs +++ b/SourceGen/FormatDescriptor.cs @@ -32,9 +32,10 @@ namespace SourceGen { /// There may be a large number of these, so try to keep the size down. These are usually /// stored in lists, not arrays, so declaring as a struct wouldn't help with that. /// - /// The stringified names of the enum values are currently serialized into the project - /// file. DO NOT rename members of the enumeration without creating an upgrade path. If - /// new values are added, the project file version number should be incremented. + /// IMPORTANT: The stringified names of the enum values are currently serialized into + /// the project file. DO NOT rename members of the enumerations without creating an + /// upgrade path. If new values are added, the project file version number should + /// be incremented. /// public class FormatDescriptor { /// diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index f55e33c..f195304 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -443,11 +443,16 @@ namespace SourceGen { /// /// Changes the pseudo-op name object. Clears the line list, instigating a - /// full re-render. + /// full re-render. If the new set is unchanged from the old set, nothing is done. /// /// Pseudo-op names. public void SetPseudoOpNames(PseudoOp.PseudoOpNames opNames) { + if (mPseudoOpNames == opNames) { + return; + } + mPseudoOpNames = opNames; + Debug.Assert(mPseudoOpNames == opNames); mLineList.Clear(); mDisplayList.Clear(); } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index d9a96f4..0336749 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -386,7 +386,7 @@ namespace SourceGen { /// Replaces the contents of the global settings object with the new settings, /// then applies them to the project. /// - /// + /// New settings. public void SetAppSettings(AppSettings settings) { AppSettings.Global.ReplaceSettings(settings); ApplyAppSettings(); @@ -450,6 +450,8 @@ namespace SourceGen { } + // Update the formatter, and null out mOutputFormatterCpuDef to force a refresh + // of related items. mOutputFormatter = new Formatter(mFormatterConfig); mOutputFormatterCpuDef = null; @@ -1410,6 +1412,8 @@ namespace SourceGen { EditAppSettings dlg = new EditAppSettings(owner, mMainWin, this, initialTab, initialAsmId); dlg.ShowDialog(); + + // The settings code calls SetAppSettings() directly whenever "Apply" is hit. } public void HandleCodeListDoubleClick(int row, int col) { diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 1d23439..1d27d85 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -59,28 +59,89 @@ namespace SourceGen { } /// - /// Pseudo-op name collection. Name strings may be null. + /// Pseudo-op name collection. Instances are immutable. /// public class PseudoOpNames { - public string EquDirective { get; set; } - public string OrgDirective { get; set; } - public string RegWidthDirective { get; set; } + public string EquDirective { get; private set; } + public string OrgDirective { get; private set; } + public string RegWidthDirective { get; private set; } - public string DefineData1 { get; set; } - public string DefineData2 { get; set; } - public string DefineData3 { get; set; } - public string DefineData4 { get; set; } - public string DefineBigData2 { get; set; } - public string DefineBigData3 { get; set; } - public string DefineBigData4 { get; set; } - public string Fill { get; set; } - public string Dense { get; set; } - public string StrGeneric { get; set; } - public string StrReverse { get; set; } - public string StrLen8 { get; set; } - public string StrLen16 { get; set; } - public string StrNullTerm { get; set; } - public string StrDci { get; set; } + public string DefineData1 { get; private set; } + public string DefineData2 { get; private set; } + public string DefineData3 { get; private set; } + public string DefineData4 { get; private set; } + public string DefineBigData2 { get; private set; } + public string DefineBigData3 { get; private set; } + public string DefineBigData4 { get; private set; } + public string Fill { get; private set; } + public string Dense { get; private set; } + public string StrGeneric { get; private set; } + public string StrReverse { get; private set; } + public string StrLen8 { get; private set; } + public string StrLen16 { get; private set; } + public string StrNullTerm { get; private set; } + public string StrDci { get; private set; } + + /// + /// Constructs an empty PseudoOp. + /// + public PseudoOpNames() : this(new Dictionary()) { + } + + /// + /// Constructor. Pass in a dictionary with name/value pairs. Unknown names + /// will be ignored, missing names will be assigned the empty string. + /// + /// Dictionary of values. + public PseudoOpNames(Dictionary dict) { + foreach (PropertyInfo prop in GetType().GetProperties()) { + dict.TryGetValue(prop.Name, out string value); + if (value == null) { + value = string.Empty; + } + prop.SetValue(this, value); + } + } + + public static bool operator ==(PseudoOpNames a, PseudoOpNames b) { + if (ReferenceEquals(a, b)) { + return true; // same object, or both null + } + if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { + return false; // one is null + } + return a.EquDirective == b.EquDirective && + a.OrgDirective == b.OrgDirective && + a.RegWidthDirective == b.RegWidthDirective && + a.DefineData1 == b.DefineData1 && + a.DefineData2 == b.DefineData2 && + a.DefineData3 == b.DefineData3 && + a.DefineData4 == b.DefineData4 && + a.DefineBigData2 == b.DefineBigData2 && + a.DefineBigData3 == b.DefineBigData3 && + a.DefineBigData4 == b.DefineBigData4 && + a.Fill == b.Fill && + a.Dense == b.Dense && + a.StrGeneric == b.StrGeneric && + a.StrReverse == b.StrReverse && + a.StrLen8 == b.StrLen8 && + a.StrLen16 == b.StrLen16 && + a.StrNullTerm == b.StrNullTerm && + a.StrDci == b.StrDci; + } + public static bool operator !=(PseudoOpNames a, PseudoOpNames b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is PseudoOpNames && this == (PseudoOpNames)obj; + } + public override int GetHashCode() { + // should be enough + return (EquDirective == null ? 0 : EquDirective.GetHashCode()) ^ + (OrgDirective == null ? 0 : OrgDirective.GetHashCode()) ^ + (DefineData1 == null ? 0 : DefineData1.GetHashCode()) ^ + (Fill == null ? 0 : Fill.GetHashCode()); + } public string GetDefineData(int width) { switch (width) { @@ -101,11 +162,6 @@ namespace SourceGen { } } - public PseudoOpNames GetCopy() { - // Do it the lazy way. - return Deserialize(Serialize()); - } - /// /// Merges the non-null, non-empty strings in "other" into this instance. /// @@ -127,13 +183,24 @@ namespace SourceGen { // which means a lot of double-quote escaping. We could do something here // that stored more nicely but it doesn't seem worth the effort. JavaScriptSerializer ser = new JavaScriptSerializer(); - return ser.Serialize(this); + + Dictionary dict = new Dictionary(); + foreach (PropertyInfo prop in GetType().GetProperties()) { + string value = (string)prop.GetValue(this); + if (!string.IsNullOrEmpty(value)) { + dict[prop.Name] = value; + } + } + + return ser.Serialize(dict); } public static PseudoOpNames Deserialize(string cereal) { JavaScriptSerializer ser = new JavaScriptSerializer(); try { - return ser.Deserialize(cereal); + Dictionary dict = + ser.Deserialize>(cereal); + return new PseudoOpNames(dict); } catch (Exception ex) { Debug.WriteLine("PseudoOpNames deserialization failed: " + ex.Message); return new PseudoOpNames(); @@ -142,34 +209,34 @@ namespace SourceGen { } /// - /// Returns a new PseudoOpNames instance with some reasonable defaults for on-screen - /// display. + /// Returns a PseudoOpNames instance with some reasonable defaults for on-screen display. /// public static PseudoOpNames DefaultPseudoOpNames { - get { return sDefaultPseudoOpNames.GetCopy(); } + get { return sDefaultPseudoOpNames; } } - private static readonly PseudoOpNames sDefaultPseudoOpNames = new PseudoOpNames() { - EquDirective = ".eq", - OrgDirective = ".org", - RegWidthDirective = ".rwid", + private static readonly PseudoOpNames sDefaultPseudoOpNames = + new PseudoOpNames(new Dictionary { + { "EquDirective", ".eq" }, + { "OrgDirective", ".org" }, + { "RegWidthDirective", ".rwid" }, - DefineData1 = ".dd1", - DefineData2 = ".dd2", - DefineData3 = ".dd3", - DefineData4 = ".dd4", - DefineBigData2 = ".dbd2", - DefineBigData3 = ".dbd3", - DefineBigData4 = ".dbd4", - Fill = ".fill", - Dense = ".bulk", + { "DefineData1", ".dd1" }, + { "DefineData2", ".dd2" }, + { "DefineData3", ".dd3" }, + { "DefineData4", ".dd4" }, + { "DefineBigData2", ".dbd2" }, + { "DefineBigData3", ".dbd3" }, + { "DefineBigData4", ".dbd4" }, + { "Fill", ".fill" }, + { "Dense", ".bulk" }, - StrGeneric = ".str", - StrReverse = ".rstr", - StrLen8 = ".l1str", - StrLen16 = ".l2str", - StrNullTerm = ".zstr", - StrDci = ".dstr", - }; + { "StrGeneric", ".str" }, + { "StrReverse", ".rstr" }, + { "StrLen8", ".l1str" }, + { "StrLen16", ".l2str" }, + { "StrNullTerm", ".zstr" }, + { "StrDci", ".dstr" } + }); /// diff --git a/SourceGen/Tests/GenTest.cs b/SourceGen/Tests/GenTest.cs index e982a46..f9413d4 100644 --- a/SourceGen/Tests/GenTest.cs +++ b/SourceGen/Tests/GenTest.cs @@ -309,7 +309,14 @@ namespace SourceGen.Tests { ReportProgress(" verify..."); timer.StartTask("Compare Binary to Expected"); FileInfo fi = new FileInfo(asmResults.OutputPathName); - if (fi.Length != project.FileData.Length) { + if (!fi.Exists) { + // This can happen if the assembler fails to generate output but doesn't + // report an error code (e.g. Merlin 32 in certain situations). + ReportErrMsg("asm output missing"); + ReportFailure(); + didFail = true; + continue; + } else if (fi.Length != project.FileData.Length) { ReportErrMsg("asm output mismatch: length is " + fi.Length + ", expected " + project.FileData.Length); ReportFailure(); diff --git a/SourceGen/WpfGui/EditAppSettings.xaml b/SourceGen/WpfGui/EditAppSettings.xaml index 6e2cdc1..774ac34 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml +++ b/SourceGen/WpfGui/EditAppSettings.xaml @@ -629,23 +629,23 @@ limitations under the License. @@ -660,19 +660,19 @@ limitations under the License. @@ -687,19 +687,19 @@ limitations under the License. @@ -712,15 +712,15 @@ limitations under the License. diff --git a/SourceGen/WpfGui/EditAppSettings.xaml.cs b/SourceGen/WpfGui/EditAppSettings.xaml.cs index b46656e..b1ca3db 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml.cs +++ b/SourceGen/WpfGui/EditAppSettings.xaml.cs @@ -30,6 +30,7 @@ using CommonUtil; using AssemblerInfo = SourceGen.AsmGen.AssemblerInfo; using AssemblerConfig = SourceGen.AsmGen.AssemblerConfig; using ExpressionMode = Asm65.Formatter.FormatConfig.ExpressionMode; +using System.Windows.Input; namespace SourceGen.WpfGui { /// @@ -212,8 +213,16 @@ namespace SourceGen.WpfGui { string stringCereal = stringSet.Serialize(); mSettings.SetString(AppSettings.FMT_STRING_DELIM, stringCereal); - mMainCtrl.SetAppSettings(mSettings); - AsmGen.AssemblerVersionCache.QueryVersions(); + try { + // QueryVersions() can sometimes be slow under Win10 (mid 2019), possibly + // because of the built-in malware detection, so pop up a wait cursor. + Mouse.OverrideCursor = Cursors.Wait; + mMainCtrl.SetAppSettings(mSettings); + AsmGen.AssemblerVersionCache.QueryVersions(); + } finally { + Mouse.OverrideCursor = null; + } + IsDirty = false; } @@ -913,9 +922,9 @@ namespace SourceGen.WpfGui { AssemblerInfo asmInfo = (AssemblerInfo)displayFmtQuickComboBox.SelectedItem; AsmGen.IGenerator gen = AssemblerInfo.GetGenerator(asmInfo.AssemblerId); - PseudoOp.PseudoOpNames opNames; + PseudoOp.PseudoOpNames unused; Asm65.Formatter.FormatConfig formatConfig; - gen.GetDefaultDisplayFormat(out opNames, out formatConfig); + gen.GetDefaultDisplayFormat(out unused, out formatConfig); SetWidthDisamSettings(formatConfig.mForceAbsOpcodeSuffix, formatConfig.mForceLongOpcodeSuffix, @@ -983,13 +992,14 @@ namespace SourceGen.WpfGui { /// Exports values from text fields to a PseudoOpNames object. /// private PseudoOp.PseudoOpNames ExportPseudoOpNames() { - PseudoOp.PseudoOpNames opNames = new PseudoOp.PseudoOpNames(); + Dictionary dict = new Dictionary(); + for (int i = 0; i < mPseudoNameMap.Length; i++) { - // NOTE: PseudoOpNames must be a class (not a struct) or this will fail. - // SetValue() would be invoked on a boxed copy that is discarded afterward. - mPseudoNameMap[i].PropInfo.SetValue(opNames, mPseudoNameMap[i].TextBox.Text); + // Use TrimEnd() to remove invisible trailing spaces, and reduce a string + // that's nothing but blanks to empty. + dict[mPseudoNameMap[i].PropInfo.Name] = mPseudoNameMap[i].TextBox.Text.TrimEnd(); } - return opNames; + return new PseudoOp.PseudoOpNames(dict); } // Invoked when text is changed in any pseudo-op text box.