Exemple #1
0
 /// <summary>
 /// Format a numeric operand value according to the specified sub-format.
 /// </summary>
 /// <param name="formatter">Text formatter.</param>
 /// <param name="symbolTable">Full table of project symbols.</param>
 /// <param name="labelMap">Symbol label remap, for local label conversion.  May be
 ///   null.</param>
 /// <param name="dfd">Operand format descriptor.</param>
 /// <param name="operandValue">Operand's value.  For most things this comes directly
 ///   out of the code, for relative branches it's a 24-bit absolute address.</param>
 /// <param name="operandLen">Length of operand, in bytes.  For an instruction, this
 ///   does not include the opcode byte.  For a relative branch, this will be 2.</param>
 /// <param name="flags">Special handling.</param>
 public static string FormatNumericOperand(Formatter formatter, SymbolTable symbolTable,
                                           Dictionary <string, string> labelMap, FormatDescriptor dfd, int operandValue,
                                           int operandLen, FormatNumericOpFlags flags)
 {
     return(FormatNumericOperand(formatter, symbolTable, null, labelMap, dfd, -1,
                                 operandValue, operandLen, flags));
 }
Exemple #2
0
        /// <summary>
        /// Format the symbol and adjustment using cc65 expression syntax.
        /// </summary>
        private static void FormatNumericSymbolCc65(Formatter formatter, Symbol sym,
                                                    Dictionary <string, string> labelMap, FormatDescriptor dfd,
                                                    int operandValue, int operandLen, FormatNumericOpFlags flags, 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)
            {
                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 (flags == FormatNumericOpFlags.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));
                }
                operandValue = (int)(operandValue & mask);

                if (sb.Length != symLabel.Length)
                {
                    sb.Append(' ');
                }
            }
            else
            {
                Debug.Assert(false, "bad numeric len");
                sb.Append("?????");
                symbolValue = 0;
            }

            adjustment = operandValue - symbolValue;

            sb.Append(formatter.FormatAdjustment(adjustment));
        }
Exemple #3
0
        /// <summary>
        /// Format the symbol and adjustment using common expression syntax.
        /// </summary>
        private static void FormatNumericSymbolCommon(Formatter formatter, Symbol sym,
                                                      Dictionary <string, string> labelMap, FormatDescriptor dfd,
                                                      int operandValue, int operandLen, FormatNumericOpFlags flags, StringBuilder sb)
        {
            // We could have some simple code that generated correct output, shifting and
            // masking every time, but that's ugly and annoying.  For single-byte ops we can
            // just use the byte-select operators, for wider ops we get only as fancy as we
            // need to be.

            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.  In 64tass the
                // selection operator has a very low precedence, similar to Merlin 32.
                string selOp;
                if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank)
                {
                    symbolValue = (sym.Value >> 16) & 0xff;
                    if (formatter.Config.mBankSelectBackQuote)
                    {
                        selOp = "`";
                    }
                    else
                    {
                        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 = "<";
                    }
                }

                operandValue &= 0xff;

                if (operandValue != symbolValue &&
                    dfd.SymbolRef.ValuePart != WeakSymbolRef.Part.Low)
                {
                    // Adjustment is required to an upper-byte part.
                    sb.Append('(');
                    sb.Append(selOp);
                    sb.Append(symLabel);
                    sb.Append(')');
                }
                else
                {
                    // no adjustment required
                    sb.Append(selOp);
                    sb.Append(symLabel);
                }
            }
            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);
                int  rightShift;
                if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank)
                {
                    symbolValue = (sym.Value >> 16);
                    rightShift  = 16;
                }
                else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High)
                {
                    symbolValue = (sym.Value >> 8);
                    rightShift  = 8;
                }
                else
                {
                    symbolValue = sym.Value;
                    rightShift  = 0;
                }

                if (flags == FormatNumericOpFlags.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;
                }

                bool needMask = false;
                if (symbolValue > mask)
                {
                    // Post-shift value won't fit in an operand-size box.
                    symbolValue = (int)(symbolValue & mask);
                    needMask    = true;
                }

                operandValue = (int)(operandValue & mask);

                // Generate one of:
                //  label [+ adj]
                //  (label >> rightShift) [+ adj]
                //  (label & mask) [+ adj]
                //  ((label >> rightShift) & mask) [+ adj]

                if (rightShift != 0 || needMask)
                {
                    if (flags != FormatNumericOpFlags.HasHashPrefix)
                    {
                        sb.Append("0+");
                    }
                    if (rightShift != 0 && needMask)
                    {
                        sb.Append("((");
                    }
                    else
                    {
                        sb.Append("(");
                    }
                }
                sb.Append(symLabel);

                if (rightShift != 0)
                {
                    sb.Append(" >> ");
                    sb.Append(rightShift.ToString());
                    sb.Append(')');
                }

                if (needMask)
                {
                    sb.Append(" & ");
                    sb.Append(formatter.FormatHexValue((int)mask, 2));
                    sb.Append(')');
                }
            }
            else
            {
                Debug.Assert(false, "bad numeric len");
                sb.Append("?????");
                symbolValue = 0;
            }

            adjustment = operandValue - symbolValue;

            sb.Append(formatter.FormatAdjustment(adjustment));
        }
Exemple #4
0
        /// <summary>
        /// Format a numeric operand value according to the specified sub-format.
        /// </summary>
        /// <param name="formatter">Text formatter.</param>
        /// <param name="symbolTable">Full table of project symbols.</param>
        /// <param name="labelMap">Symbol label remap, for local label conversion.  May be
        ///   null.</param>
        /// <param name="dfd">Operand format descriptor.</param>
        /// <param name="operandValue">Operand's value.  For most things this comes directly
        ///   out of the code, for relative branches it's a 24-bit absolute address.</param>
        /// <param name="operandLen">Length of operand, in bytes.  For an instruction, this
        ///   does not include the opcode byte.  For a relative branch, this will be 2.</param>
        /// <param name="flags">Special handling.</param>
        public static string FormatNumericOperand(Formatter formatter, SymbolTable symbolTable,
                                                  Dictionary <string, string> labelMap, FormatDescriptor dfd,
                                                  int operandValue, int operandLen, FormatNumericOpFlags flags)
        {
            Debug.Assert(operandLen > 0);
            int hexMinLen = operandLen * 2;

            switch (dfd.FormatSubType)
            {
            case FormatDescriptor.SubType.None:
            case FormatDescriptor.SubType.Hex:
            case FormatDescriptor.SubType.Address:
                return(formatter.FormatHexValue(operandValue, hexMinLen));

            case FormatDescriptor.SubType.Decimal:
                return(formatter.FormatDecimalValue(operandValue));

            case FormatDescriptor.SubType.Binary:
                return(formatter.FormatBinaryValue(operandValue, hexMinLen * 4));

            case FormatDescriptor.SubType.Ascii:
                return(formatter.FormatAsciiOrHex(operandValue));

            case FormatDescriptor.SubType.Symbol:
                if (symbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym))
                {
                    StringBuilder sb = new StringBuilder();

                    switch (formatter.ExpressionMode)
                    {
                    case Formatter.FormatConfig.ExpressionMode.Common:
                        FormatNumericSymbolCommon(formatter, sym, labelMap,
                                                  dfd, operandValue, operandLen, flags, sb);
                        break;

                    case Formatter.FormatConfig.ExpressionMode.Cc65:
                        FormatNumericSymbolCc65(formatter, sym, labelMap,
                                                dfd, operandValue, operandLen, flags, sb);
                        break;

                    case Formatter.FormatConfig.ExpressionMode.Merlin:
                        FormatNumericSymbolMerlin(formatter, sym, labelMap,
                                                  dfd, operandValue, operandLen, flags, sb);
                        break;

                    default:
                        Debug.Assert(false, "Unknown expression mode " +
                                     formatter.ExpressionMode);
                        return("???");
                    }

                    return(sb.ToString());
                }
                else
                {
                    return(formatter.FormatHexValue(operandValue, hexMinLen));
                }

            default:
                Debug.Assert(false);
                return("???");
            }
        }
Exemple #5
0
        /// <summary>
        /// Format the symbol and adjustment using Merlin expression syntax.
        /// </summary>
        private static void FormatNumericSymbolMerlin(Formatter formatter, Symbol sym,
                                                      Dictionary <string, string> labelMap, FormatDescriptor dfd,
                                                      int operandValue, int operandLen, FormatNumericOpFlags flags, 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.  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.
            //
            // This behavior leads to simpler expressions for simple symbol adjustments.

            string symLabel = sym.Label;

            if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel))
            {
                symLabel = newLabel;
            }

            int adjustment;

            // 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 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 | (int)(sym.Value & 0xff00ffff);
                keepLen    = 3;
            }
            else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High)
            {
                adjOperand = (operandValue << 8) | (sym.Value & 0xff);
                keepLen    = 2;
            }
            else
            {
                adjOperand = operandValue;
                keepLen    = 1;
            }

            keepLen    = Math.Max(keepLen, operandLen);
            adjustment = adjOperand - sym.Value;
            if (keepLen == 1)
            {
                adjustment %= 256;
                // 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*/;
                }
                else if (adjustment < -128)
                {
                    adjustment = (256 + adjustment) /*% 256*/;
                }
            }
            else if (keepLen == 2)
            {
                adjustment %= 65536;
                if (adjustment > 32767)
                {
                    adjustment = -(65536 - adjustment) /*% 65536*/;
                }
                else if (adjustment < -32768)
                {
                    adjustment = (65536 + adjustment) /*% 65536*/;
                }
            }

            // Use the label from sym, not dfd's weak ref; might be different if label
            // comparisons are case-insensitive.
            switch (dfd.SymbolRef.ValuePart)
            {
            case WeakSymbolRef.Part.Unknown:
            case WeakSymbolRef.Part.Low:
                // For Merlin, "<" is effectively a no-op.  We can put it in for
                // aesthetics when grabbing the low byte of a 16-bit value.
                if ((operandLen == 1) && sym.Value > 0xff)
                {
                    sb.Append('<');
                }
                sb.Append(symLabel);
                break;

            case WeakSymbolRef.Part.High:
                sb.Append('>');
                sb.Append(symLabel);
                break;

            case WeakSymbolRef.Part.Bank:
                sb.Append('^');
                sb.Append(symLabel);
                break;

            default:
                Debug.Assert(false, "bad part");
                sb.Append("???");
                break;
            }

            sb.Append(formatter.FormatAdjustment(adjustment));
        }
Exemple #6
0
        /// <summary>
        /// Format a numeric operand value according to the specified sub-format.
        /// </summary>
        /// <param name="formatter">Text formatter.</param>
        /// <param name="symbolTable">Full table of project symbols.</param>
        /// <param name="lvLookup">Local variable lookup object.  May be null if not
        ///   formatting an instruction.</param>
        /// <param name="labelMap">Symbol label remap, for local label conversion.  May be
        ///   null.</param>
        /// <param name="dfd">Operand format descriptor.</param>
        /// <param name="offset">Offset of start of instruction or data pseudo-op, for
        ///   variable name lookup.  Okay to pass -1 when not formatting an instruction.</param>
        /// <param name="operandValue">Operand's value.  For most things this comes directly
        ///   out of the code, for relative branches it's a 24-bit absolute address.</param>
        /// <param name="operandLen">Length of operand, in bytes.  For an instruction, this
        ///   does not include the opcode byte.  For a relative branch, this will be 2.</param>
        /// <param name="flags">Special handling.</param>
        public static string FormatNumericOperand(Formatter formatter, SymbolTable symbolTable,
                                                  LocalVariableLookup lvLookup, Dictionary <string, string> labelMap,
                                                  FormatDescriptor dfd, int offset, int operandValue, int operandLen,
                                                  FormatNumericOpFlags flags)
        {
            Debug.Assert(operandLen > 0);
            int hexMinLen = operandLen * 2;

            switch (dfd.FormatSubType)
            {
            case FormatDescriptor.SubType.None:
            case FormatDescriptor.SubType.Hex:
            case FormatDescriptor.SubType.Address:
                return(formatter.FormatHexValue(operandValue, hexMinLen));

            case FormatDescriptor.SubType.Decimal:
                return(formatter.FormatDecimalValue(operandValue));

            case FormatDescriptor.SubType.Binary:
                return(formatter.FormatBinaryValue(operandValue, hexMinLen * 4));

            case FormatDescriptor.SubType.Ascii:
            case FormatDescriptor.SubType.HighAscii:
            case FormatDescriptor.SubType.C64Petscii:
            case FormatDescriptor.SubType.C64Screen:
                CharEncoding.Encoding enc = SubTypeToEnc(dfd.FormatSubType);
                return(formatter.FormatCharacterValue(operandValue, enc));

            case FormatDescriptor.SubType.Symbol:
                if (lvLookup != null && dfd.SymbolRef.IsVariable)
                {
                    Debug.Assert(operandLen == 1);          // only doing 8-bit stuff
                    DefSymbol defSym = lvLookup.GetSymbol(offset, dfd.SymbolRef);
                    if (defSym != null)
                    {
                        StringBuilder sb = new StringBuilder();
                        FormatNumericSymbolCommon(formatter, defSym, null,
                                                  dfd, operandValue, operandLen, flags, sb);
                        return(sb.ToString());
                    }
                    else
                    {
                        Debug.WriteLine("Local variable format failed");
                        Debug.Assert(false);
                        return(formatter.FormatHexValue(operandValue, hexMinLen));
                    }
                }
                else if (symbolTable.TryGetNonVariableValue(dfd.SymbolRef.Label,
                                                            out Symbol sym))
                {
                    StringBuilder sb = new StringBuilder();

                    switch (formatter.ExpressionMode)
                    {
                    case Formatter.FormatConfig.ExpressionMode.Common:
                        FormatNumericSymbolCommon(formatter, sym, labelMap,
                                                  dfd, operandValue, operandLen, flags, sb);
                        break;

                    case Formatter.FormatConfig.ExpressionMode.Cc65:
                        FormatNumericSymbolCc65(formatter, sym, labelMap,
                                                dfd, operandValue, operandLen, flags, sb);
                        break;

                    case Formatter.FormatConfig.ExpressionMode.Merlin:
                        FormatNumericSymbolMerlin(formatter, sym, labelMap,
                                                  dfd, operandValue, operandLen, flags, sb);
                        break;

                    default:
                        Debug.Assert(false, "Unknown expression mode " +
                                     formatter.ExpressionMode);
                        return("???");
                    }

                    return(sb.ToString());
                }
                else
                {
                    return(formatter.FormatHexValue(operandValue, hexMinLen));
                }

            default:
                // should not see REMOVE or ASCII_GENERIC here
                Debug.Assert(false);
                return("???");
            }
        }
Exemple #7
0
        /// <summary>
        /// Format the symbol and adjustment using Merlin expression syntax.
        /// </summary>
        private static void FormatNumericSymbolMerlin(Formatter formatter, Symbol sym,
                                                      Dictionary <string, string> labelMap, FormatDescriptor dfd,
                                                      int operandValue, int operandLen, FormatNumericOpFlags flags, 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.  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.
            //
            // This behavior leads to simpler expressions for simple symbol adjustments.

            string symLabel = sym.Label;

            if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel))
            {
                symLabel = newLabel;
            }

            int adjustment;

            // 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 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 | (int)(sym.Value & 0xff00ffff);
                keepLen    = 3;
            }
            else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High)
            {
                adjOperand = (operandValue << 8) | (sym.Value & 0xff);
                keepLen    = 2;
            }
            else
            {
                adjOperand = operandValue;
                keepLen    = 1;
            }

            keepLen    = Math.Max(keepLen, operandLen);
            adjustment = adjOperand - sym.Value;

            if (keepLen == 1)
            {
                int origAdjust = adjustment;
                adjustment %= 256;
                // 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*/;
                }
                else if (adjustment < -128)
                {
                    adjustment = (256 + adjustment) /*% 256*/;
                }

                // We have a problem with ambiguous direct-page arguments if the adjusted
                // value crosses a bank boundary.  For example, "LDA $fff0+24" is computed
                // as $010008, which is too big for a DP arg, so Merlin treats it as absolute
                // (LDA $0008) instead of DP.  If Merlin had done the implicit "& $ffff" before
                // testing the value for DP range, this would behave correctly.  Unfortunately
                // there is no "force DP" modifier, so we either need to add an explicit mask
                // or just punt and use the original adjustment.
                // TODO(someday): we only need to do this for ambiguous DP.  If the instruction
                // is imm or doesn't have an abs equivalent, or it's a fixed-width data item
                // like .DD1, we can still use the nicer-looking adjustment.  We don't currently
                // pass the OpDef in here.
                if ((sym.Value & 0xff0000) != ((sym.Value + adjustment) & 0xff0000))
                {
                    adjustment = origAdjust;
                }
            }
            else if (keepLen == 2)
            {
                adjustment %= 65536;
                if (adjustment > 32767)
                {
                    adjustment = -(65536 - adjustment) /*% 65536*/;
                }
                else if (adjustment < -32768)
                {
                    adjustment = (65536 + adjustment) /*% 65536*/;
                }
            }

            // Use the label from sym, not dfd's weak ref; might be different if label
            // comparisons are case-insensitive.
            switch (dfd.SymbolRef.ValuePart)
            {
            case WeakSymbolRef.Part.Unknown:
            case WeakSymbolRef.Part.Low:
                // For Merlin, "<" is effectively a no-op.  We can put it in for
                // aesthetics when grabbing the low byte of a 16-bit value.
                if ((operandLen == 1) && sym.Value > 0xff)
                {
                    sb.Append('<');
                }
                sb.Append(symLabel);
                break;

            case WeakSymbolRef.Part.High:
                sb.Append('>');
                sb.Append(symLabel);
                break;

            case WeakSymbolRef.Part.Bank:
                sb.Append('^');
                sb.Append(symLabel);
                break;

            default:
                Debug.Assert(false, "bad part");
                sb.Append("???");
                break;
            }

            sb.Append(formatter.FormatAdjustment(adjustment));
        }