diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index cf47027..31eb551 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -54,6 +54,7 @@ namespace Asm65 { public bool mUpperOperandA; // display acc operand in upper case? public bool mUpperOperandS; // display stack operand in upper case? public bool mUpperOperandXY; // display index register operand in upper case? + public bool mBankSelectBackQuote; // use '`' rather than '^' for bank select? public bool mAddSpaceLongComment; // insert space after delimiter for long comments? // functional changes to assembly output @@ -79,7 +80,8 @@ namespace Asm65 { public enum CharConvMode { Unknown = 0, PlainAscii, HighLowAscii }; public CharConvMode mHexDumpCharConvMode; // character conversion mode for dumps - public enum ExpressionMode { Unknown = 0, Simple, Merlin }; + // Hopefully we don't need a separate mode for every assembler in existence. + public enum ExpressionMode { Unknown = 0, Simple, Cc65, Merlin }; public ExpressionMode mExpressionMode; // symbol rendering mode // Deserialization helper. @@ -601,22 +603,6 @@ namespace Asm65 { return fmt; } - /// - /// Formats a pseudo-opcode. - /// - /// Pseudo-op string to format. - /// Formatted string. - public string FormatPseudoOp(string opstr) { - if (!mPseudoOpStrings.TryGetValue(opstr, out string result)) { - if (mFormatConfig.mUpperPseudoOpcodes) { - result = mPseudoOpStrings[opstr] = opstr.ToUpperInvariant(); - } else { - result = mPseudoOpStrings[opstr] = opstr; - } - } - return result; - } - /// /// Formats the instruction operand. /// @@ -634,6 +620,22 @@ namespace Asm65 { return string.Format(format, contents); } + /// + /// Formats a pseudo-opcode. + /// + /// Pseudo-op string to format. + /// Formatted string. + public string FormatPseudoOp(string opstr) { + if (!mPseudoOpStrings.TryGetValue(opstr, out string result)) { + if (mFormatConfig.mUpperPseudoOpcodes) { + result = mPseudoOpStrings[opstr] = opstr.ToUpperInvariant(); + } else { + result = mPseudoOpStrings[opstr] = opstr; + } + } + return result; + } + /// /// Generates a format string for N hex bytes. /// diff --git a/SourceGen/AppForms/EditAppSettings.cs b/SourceGen/AppForms/EditAppSettings.cs index b1bb30a..a431f84 100644 --- a/SourceGen/AppForms/EditAppSettings.cs +++ b/SourceGen/AppForms/EditAppSettings.cs @@ -611,7 +611,7 @@ namespace SourceGen.AppForms { private void shiftAfterAdjustCheckBox_CheckedChanged(object sender, EventArgs e) { string mode = useMerlinExpressions.Checked ? Asm65.Formatter.FormatConfig.ExpressionMode.Merlin.ToString() : - Asm65.Formatter.FormatConfig.ExpressionMode.Simple.ToString(); + Asm65.Formatter.FormatConfig.ExpressionMode.Cc65.ToString(); mSettings.SetString(AppSettings.FMT_EXPRESSION_MODE, mode); SetDirty(true); } diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 45701d2..08d07a9 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -188,7 +188,7 @@ namespace SourceGen.AsmGen { config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; config.mAllowHighAsciiCharConst = false; - config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Simple; + config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Cc65; } // IGenerator diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 2df7809..56231b5 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -168,6 +168,15 @@ namespace SourceGen.AsmGen { /// Configures the assembler-specific format items. /// private void SetFormatConfigValues(ref Formatter.FormatConfig config) { + // Must be lower case when --case-sensitive is used. + config.mUpperOpcodes = false; + config.mUpperPseudoOpcodes = false; + config.mUpperOperandA = false; + config.mUpperOperandS = false; + config.mUpperOperandXY = false; + + config.mBankSelectBackQuote = true; + config.mForceAbsOpcodeSuffix = string.Empty; config.mForceLongOpcodeSuffix = string.Empty; config.mForceAbsOperandPrefix = "@w"; // word @@ -176,11 +185,6 @@ namespace SourceGen.AsmGen { config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; config.mAllowHighAsciiCharConst = false; - config.mUpperOpcodes = false; - config.mUpperPseudoOpcodes = false; - config.mUpperOperandA = false; - config.mUpperOperandS = false; - config.mUpperOperandXY = false; config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Simple; } diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index 0da6775..dc6213a 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -202,7 +202,8 @@ namespace SourceGen.AsmGen { operandForSymbol = attr.OperandAddress; } - // Check Length to watch for bogus descriptors (?) + // Check Length to watch for bogus descriptors. (ApplyFormatDescriptors() should + // now be screening bad descriptors out, so we may not need the Length test.) if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) { // Format operand as directed. if (op.AddrMode == OpDef.AddressMode.BlockMove) { diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index a12de80..63ae82a 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -541,6 +541,10 @@ namespace SourceGen { FormatNumericSymbolSimple(formatter, sym, labelMap, dfd, operandValue, operandLen, isPcRel, sb); break; + case Formatter.FormatConfig.ExpressionMode.Cc65: + FormatNumericSymbolCc65(formatter, sym, labelMap, + dfd, operandValue, operandLen, isPcRel, sb); + break; case Formatter.FormatConfig.ExpressionMode.Merlin: FormatNumericSymbolMerlin(formatter, sym, labelMap, dfd, operandValue, operandLen, isPcRel, sb); @@ -584,7 +588,11 @@ namespace SourceGen { string selOp; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { symbolValue = (sym.Value >> 16) & 0xff; - selOp = "^"; + if (formatter.Config.mBankSelectBackQuote) { + selOp = "`"; + } else { + selOp = "^"; + } } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { symbolValue = (sym.Value >> 8) & 0xff; selOp = ">"; @@ -651,16 +659,113 @@ namespace SourceGen { sb.Append(formatter.FormatAdjustment(adjustment)); } + /// + /// Format the symbol and adjustment using cc65 expression syntax. + /// + private static void FormatNumericSymbolCc65(Formatter formatter, Symbol sym, + Dictionary labelMap, FormatDescriptor dfd, + int operandValue, int operandLen, bool isPcRel, StringBuilder sb) { + // The key difference between cc65 and other assemblers with general expressions + // is that the bitwise shift and AND operators have higher precedence than the + // arithmetic ops like add and subtract. (The bitwise ops are equal to multiply + // and divide.) This means that, if we want to mask off the low 16 bits and add one + // to a label, we can write "start & $ffff + 1" rather than "(start & $ffff) + 1". + // + // This is particularly convenient for PEA, since "PEA (start & $ffff)" looks like + // we're trying to use a (non-existent) indirect form of PEA. We can write things + // in a simpler way. + + int adjustment, symbolValue; + + string symLabel = sym.Label; + if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { + symLabel = newLabel; + } + + if (operandLen == 1) { + // Use the byte-selection operator to get the right piece. + string selOp; + if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { + symbolValue = (sym.Value >> 16) & 0xff; + selOp = "^"; + } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { + symbolValue = (sym.Value >> 8) & 0xff; + selOp = ">"; + } else { + symbolValue = sym.Value & 0xff; + if (symbolValue == sym.Value) { + selOp = string.Empty; + } else { + selOp = "<"; + } + } + sb.Append(selOp); + sb.Append(symLabel); + + operandValue &= 0xff; + } else if (operandLen <= 4) { + // Operands and values should be 8/16/24 bit unsigned quantities. 32-bit + // support is really there so you can have a 24-bit pointer in a 32-bit hole. + // Might need to adjust this if 32-bit signed quantities become interesting. + uint mask = 0xffffffff >> ((4 - operandLen) * 8); + string shOp; + if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { + symbolValue = (sym.Value >> 16); + shOp = " >> 16"; + } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { + symbolValue = (sym.Value >> 8); + shOp = " >> 8"; + } else { + symbolValue = sym.Value; + shOp = ""; + } + + if (isPcRel) { + // PC-relative operands are funny, because an 8- or 16-bit value is always + // expanded to 24 bits. We output a 16-bit value that the assembler will + // convert back to 8-bit or 16-bit. In any event, the bank byte is never + // relevant to our computations. + operandValue &= 0xffff; + symbolValue &= 0xffff; + } + + sb.Append(symLabel); + sb.Append(shOp); + if (symbolValue > mask) { + // Post-shift value won't fit in an operand-size box. + symbolValue = (int)(symbolValue & mask); + sb.Append(" & "); + sb.Append(formatter.FormatHexValue((int)mask, 2)); + } + + if (sb.Length != symLabel.Length) { + sb.Append(' '); + } + + operandValue = (int)(operandValue & mask); + } else { + Debug.Assert(false, "bad numeric len"); + sb.Append("?????"); + symbolValue = 0; + } + + adjustment = operandValue - symbolValue; + + sb.Append(formatter.FormatAdjustment(adjustment)); + } + /// /// Format the symbol and adjustment using Merlin expression syntax. /// private static void FormatNumericSymbolMerlin(Formatter formatter, Symbol sym, Dictionary labelMap, FormatDescriptor dfd, int operandValue, int operandLen, bool isPcRel, StringBuilder sb) { + // Merlin expressions are compatible with the original 8-bit Merlin. They're + // evaluated from left to right, with (almost) no regard for operator precedence. + // // The part-selection operators differ from "simple" in two ways: - // (1) They always happen last. If FOO=$10f0, "#>FOO+$18" == $11. (Strangely, - // all other operators are evaluated from left to right, with no concept - // of operator precedence.) + // (1) They always happen last. If FOO=$10f0, "#>FOO+$18" == $11. One of the + // few cases where left-to-right evaluation is overridden. // (2) They select words, not bytes. If FOO=$123456, "#>FOO" is $1234. This is // best thought of as a shift operator, rather than byte-selection. For // 8-bit code this doesn't matter. @@ -676,13 +781,13 @@ namespace SourceGen { // If we add or subtract an adjustment, it will be done on the full value, which // is then shifted to the appropriate part. So we need to left-shift the operand - // value to match. We fill in the low bytes with the contes of the symbol, so + // value to match. We fill in the low bytes with the contents of the symbol, so // that the adjustment doesn't include unnecessary values. (For example, let // FOO=$10f0, with operand "#>FOO" ($10). We shift the operand to get $1000, then // OR in the low byte to get $10f0, so that when we subtract we get adjustment==0.) int adjOperand, keepLen; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { - adjOperand = operandValue << 16 | (sym.Value & 0xffff); + adjOperand = operandValue << 16 | (int)(sym.Value & 0xff00ffff); keepLen = 3; } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { adjOperand = (operandValue << 8) | (sym.Value & 0xff); @@ -696,7 +801,7 @@ namespace SourceGen { adjustment = adjOperand - sym.Value; if (keepLen == 1) { adjustment %= 256; - // Adjust for aesthetics. The assembler implicitly appiles a modulo operation, + // Adjust for aesthetics. The assembler implicitly applies a modulo operation, // so we can use the value closest to zero. if (adjustment > 127) { adjustment = -(256 - adjustment) /*% 256*/; diff --git a/SourceGen/RuntimeData/Help/codegen.html b/SourceGen/RuntimeData/Help/codegen.html index 913dc21..55e4363 100644 --- a/SourceGen/RuntimeData/Help/codegen.html +++ b/SourceGen/RuntimeData/Help/codegen.html @@ -198,7 +198,7 @@ code, but also needs to know how to handle the corner cases.

left to right, with no operator precedence.
  • The byte selection operators ('<', '>', '^') are actually word-selection operators, yielding 16-bit values when wide registers - are enabled on the 65816.

    + are enabled on the 65816.
  • The assembler tracks register widths when it sees SEP/REP instructions, but doesn't attempt to track the emulation flag. So if the registers are long when you switch to emulation, incorrect code is generated. diff --git a/SourceGen/SGTestData/2007-labels-and-symbols b/SourceGen/SGTestData/2007-labels-and-symbols index 2e9884f..a200c62 100644 Binary files a/SourceGen/SGTestData/2007-labels-and-symbols and b/SourceGen/SGTestData/2007-labels-and-symbols differ diff --git a/SourceGen/SGTestData/2007-labels-and-symbols.dis65 b/SourceGen/SGTestData/2007-labels-and-symbols.dis65 index 87b4219..39f1abd 100644 --- a/SourceGen/SGTestData/2007-labels-and-symbols.dis65 +++ b/SourceGen/SGTestData/2007-labels-and-symbols.dis65 @@ -1,6 +1,6 @@ ### 6502bench SourceGen dis65 v1.0 ### { -"_ContentVersion":1,"FileDataLength":678,"FileDataCrc32":1825804405,"ProjectProps":{ +"_ContentVersion":1,"FileDataLength":695,"FileDataCrc32":1242388944,"ProjectProps":{ "CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":33489103,"AnalysisParams":{ "AnalyzeUncategorizedData":true,"MinCharsForString":4,"SeekNearbyTargets":true}, "PlatformSymbolFileIdentifiers":["PROJ:2007-labels-and-symbols.sym65","RT:TestData/TestSyms.sym65"],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ @@ -19,7 +19,11 @@ "projover":{ "DataDescriptor":{ "Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, -"Comment":"replaces platform symbol","Label":"projover","Value":16384,"Source":"Project","Type":"ExternalAddr"}}}, +"Comment":"replaces platform symbol","Label":"projover","Value":16384,"Source":"Project","Type":"ExternalAddr"}, +"thirty2":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"32-bit constant test","Label":"thirty2","Value":305419896,"Source":"Project","Type":"Constant"}}}, "AddressMap":[{ "Offset":0,"Addr":74565}, { @@ -30,7 +34,7 @@ "397":"bulky","509":"stringy"}, "LongComments":{ "-2147483647":{ -"Text":"Project was edited to add a label in the middle of a dense hex region, and add a duplicate label.","BoxMode":false,"MaxWidth":80}}, +"Text":"Project was edited to add a label in the middle of a dense hex region, and add a duplicate label.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, "Notes":{ }, "UserLabels":{ @@ -381,4 +385,22 @@ "Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ "Label":"badmid","Part":"Low"}}, "397":{ -"Length":112,"Format":"Dense","SubFormat":"None","SymbolRef":null}}} +"Length":112,"Format":"Dense","SubFormat":"None","SymbolRef":null}, +"677":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"thirty2","Part":"Low"}}, +"679":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"thirty2","Part":"High"}}, +"681":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"thirty2","Part":"Bank"}}, +"685":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"thirty2","Part":"Low"}}, +"688":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"thirty2","Part":"High"}}, +"691":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"thirty2","Part":"Bank"}}}} diff --git a/SourceGen/SGTestData/Expected/2007-labels-and-symbols_Merlin32.S b/SourceGen/SGTestData/Expected/2007-labels-and-symbols_Merlin32.S index a4c5ae1..c58c772 100644 --- a/SourceGen/SGTestData/Expected/2007-labels-and-symbols_Merlin32.S +++ b/SourceGen/SGTestData/Expected/2007-labels-and-symbols_Merlin32.S @@ -6,6 +6,7 @@ plataddr equ $3000 ;address only in platform file projalsa equ $3200 ;same val as projalso absh equ $feed biggie equ $123456 +thirty2 equ $12345678 ;32-bit constant test org $012345 start clc @@ -186,5 +187,13 @@ string asc 'This is a long string. Put a label and comment on it to confir' asc 'm that the label and comment only appear on the first line. T' asc 'he quick brown fox jumps over the lazy dogs.' -L118E rts +L118E lda #thirty2+768 + lda #^thirty2 + rep #$30 + mx %00 + lda #thirty2+3 + lda #>thirty2+1024 + lda #^thirty2 + rts diff --git a/SourceGen/SGTestData/Expected/2007-labels-and-symbols_cc65.S b/SourceGen/SGTestData/Expected/2007-labels-and-symbols_cc65.S index 487b203..52361ef 100644 --- a/SourceGen/SGTestData/Expected/2007-labels-and-symbols_cc65.S +++ b/SourceGen/SGTestData/Expected/2007-labels-and-symbols_cc65.S @@ -7,6 +7,7 @@ plataddr = $3000 ;address only in platform file projalsa = $3200 ;same val as projalso absh = $feed biggie = $123456 +thirty2 = $12345678 ;32-bit constant test .org $012345 .a8 @@ -194,5 +195,14 @@ string: .byte "This is a long string. Put a label and comment on it to confir .byte "m that the label and comment only appear on the first line. T" .byte "he quick brown fox jumps over the lazy dogs." -L118E: rts +L118E: lda #thirty2+3 + lda #^thirty2 + rep #$30 + .a16 + .i16 + lda #thirty2 & $ffff +3 + lda #thirty2 >> 8 & $ffff +4 + lda #thirty2 >> 16 + rts diff --git a/SourceGen/SGTestData/Source/2007-labels-and-symbols.S b/SourceGen/SGTestData/Source/2007-labels-and-symbols.S index 0792e53..2c3b071 100644 --- a/SourceGen/SGTestData/Source/2007-labels-and-symbols.S +++ b/SourceGen/SGTestData/Source/2007-labels-and-symbols.S @@ -7,6 +7,7 @@ absl equ $1029 absh equ $feed zip equ $cd biggie equ $123456 +thirty2 equ $12345678 org $012345 @@ -217,5 +218,15 @@ t4c jml target2 :skiphex +; extract bytes from 32-bit value with short regs + lda #thirty2 + 768 + lda #^thirty2 + rep #$30 + mx %00 + lda #thirty2 + 1024 + lda #^thirty2 + rts