// IGenerator public void OutputDataOp(int offset) { Formatter formatter = SourceFormatter; byte[] data = Project.FileData; Anattrib attr = Project.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = mLocalizer.ConvLabel(attr.Symbol.Label); } string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]); string opcodeStr, operandStr; FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); int length = dfd.Length; Debug.Assert(length > 0); bool multiLine = false; switch (dfd.FormatType) { case FormatDescriptor.Type.Default: if (length != 1) { Debug.Assert(false); length = 1; } opcodeStr = sDataOpNames.DefineData1; int operand = RawData.GetWord(data, offset, length, false); operandStr = formatter.FormatHexValue(operand, length * 2); break; case FormatDescriptor.Type.NumericLE: opcodeStr = sDataOpNames.GetDefineData(length); operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); if ((string.IsNullOrEmpty(opcodeStr))) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); } break; case FormatDescriptor.Type.Fill: opcodeStr = sDataOpNames.Fill; operandStr = length + "," + formatter.FormatHexValue(data[offset], 2); break; case FormatDescriptor.Type.Dense: multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; case FormatDescriptor.Type.Uninit: case FormatDescriptor.Type.Junk: // The ca65 .align directive has a dependency on the alignment of the // segment as a whole. We're not currently declaring multiple segments, // so we can't use .align without generating complaints. int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); if (fillVal >= 0 && (length > 1 || fillVal == 0x00)) { // If multi-byte, or single byte and zero, treat same as Fill. opcodeStr = sDataOpNames.Fill; operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); } else { // treat same as Dense multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); } break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: case FormatDescriptor.Type.StringL8: case FormatDescriptor.Type.StringL16: case FormatDescriptor.Type.StringDci: multiLine = true; opcodeStr = operandStr = null; OutputString(offset, labelStr, commentStr); break; default: opcodeStr = "???"; operandStr = "***"; break; } if (!multiLine) { opcodeStr = formatter.FormatPseudoOp(opcodeStr); OutputLine(labelStr, opcodeStr, operandStr, commentStr); } }
private void OutputString(int offset, string labelStr, string commentStr) { // Normal ASCII strings are straightforward: they're just part of a .byte // directive, and can mix with anything else in the .byte. // // For CString we can use .asciiz, but only if the string fits on one line // and doesn't include delimiters. For L8String and L16String we can // define simple macros, but their use has a similar restriction. High-ASCII // strings also require a macro. // // We might be able to define a macro for DCI and Reverse as well. // // The limitation on strings with delimiters arises because (1) I don't see a // way to escape them within a string, and (2) the simple macro workarounds // only take a single argument, not a comma-separated list of stuff. // // Some ideas here: // https://groups.google.com/forum/#!topic/comp.sys.apple2.programmer/5Wkw8mUPcU0 Anattrib attr = Project.GetAnattrib(offset); FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); Debug.Assert(dfd.IsString); Debug.Assert(dfd.Length > 0); CharEncoding.Convert charConv; bool isHighAscii = false; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: charConv = CharEncoding.ConvertAscii; break; case FormatDescriptor.SubType.HighAscii: if (dfd.FormatType != FormatDescriptor.Type.StringGeneric) { OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } charConv = CharEncoding.ConvertHighAscii; isHighAscii = true; break; case FormatDescriptor.SubType.C64Petscii: case FormatDescriptor.SubType.C64Screen: default: OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } Formatter formatter = SourceFormatter; byte[] data = Project.FileData; int leadingBytes = 0; int trailingBytes = 0; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringDci: break; case FormatDescriptor.Type.StringNullTerm: trailingBytes = 1; break; case FormatDescriptor.Type.StringL8: leadingBytes = 1; break; case FormatDescriptor.Type.StringL16: leadingBytes = 2; break; default: Debug.Assert(false); return; } StringOpFormatter stropf = new StringOpFormatter(SourceFormatter, Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, charConv, false); stropf.FeedBytes(data, offset, dfd.Length - trailingBytes, leadingBytes, StringOpFormatter.ReverseMode.Forward); string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.StrGeneric); if (isHighAscii) { // Does this fit the narrow definition of what we can do with a macro? Debug.Assert(dfd.FormatType == FormatDescriptor.Type.StringGeneric); if (stropf.Lines.Count == 1 && !stropf.HasEscapedText) { if (!mHighAsciiMacroOutput) { mHighAsciiMacroOutput = true; // Output a macro for high-ASCII strings. // // TODO(maybe): the preferred way to do this is apparently // ".macpack apple2" to load some standard macros, then e.g. // scrcode "My high-ASCII string". The macro takes 9 arguments and // recognizes characters and numbers, so it should be possible to // mix strings, string delimiters, and control chars so long as the // argument count is not exceeded. OutputLine(".macro", "HiAscii", "Arg", string.Empty); OutputLine(string.Empty, ".repeat", ".strlen(Arg), I", string.Empty); OutputLine(string.Empty, ".byte", ".strat(Arg, I) | $80", string.Empty); OutputLine(string.Empty, ".endrep", string.Empty, string.Empty); OutputLine(".endmacro", string.Empty, string.Empty, string.Empty); } opcodeStr = formatter.FormatPseudoOp("HiAscii"); } else { // didn't work out, dump hex OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } } if (dfd.FormatType == FormatDescriptor.Type.StringNullTerm) { if (stropf.Lines.Count == 1 && !stropf.HasEscapedText) { // Keep it. opcodeStr = sDataOpNames.StrNullTerm; } else { // Didn't fit, so re-emit it, this time with the terminating null byte. stropf.Reset(); stropf.FeedBytes(data, offset, dfd.Length, leadingBytes, StringOpFormatter.ReverseMode.Forward); } } foreach (string str in stropf.Lines) { OutputLine(labelStr, opcodeStr, str, commentStr); labelStr = commentStr = string.Empty; // only show on first } }
private static void GenerateInstruction(IGenerator gen, StreamWriter sw, int offset, int instrBytes, bool doAddCycles) { DisasmProject proj = gen.Project; Formatter formatter = gen.SourceFormatter; byte[] data = proj.FileData; Anattrib attr = proj.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label); } OpDef op = proj.CpuDef.GetOpDef(data[offset]); int operand = op.GetOperand(data, offset, attr.StatusFlags); int instrLen = op.GetLength(attr.StatusFlags); OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; if (op.IsWidthPotentiallyAmbiguous) { wdis = OpDef.GetWidthDisambiguation(instrLen, operand); } if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None && (op.AddrMode == OpDef.AddressMode.DP || op.AddrMode == OpDef.AddressMode.DPIndexX) || op.AddrMode == OpDef.AddressMode.DPIndexY) { // Could be a forward reference to a direct-page label. if (IsForwardLabelReference(gen, offset)) { wdis = OpDef.WidthDisambiguation.ForceDirect; } } string opcodeStr = formatter.FormatOpcode(op, wdis); string formattedOperand = null; int operandLen = instrLen - 1; PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None; bool isPcRelBankWrap = false; // Tweak branch instructions. We want to show the absolute address rather // than the relative offset (which happens with the OperandAddress assignment // below), and 1-byte branches should always appear as a 4-byte hex value. if (op.AddrMode == OpDef.AddressMode.PCRel) { Debug.Assert(attr.OperandAddress >= 0); operandLen = 2; opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || op.AddrMode == OpDef.AddressMode.StackPCRelLong) { opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.Imm || op.AddrMode == OpDef.AddressMode.ImmLongA || op.AddrMode == OpDef.AddressMode.ImmLongXY) { opFlags = PseudoOp.FormatNumericOpFlags.HasHashPrefix; } if (opFlags == PseudoOp.FormatNumericOpFlags.IsPcRel) { int branchDist = attr.Address - attr.OperandAddress; isPcRelBankWrap = branchDist > 32767 || branchDist < -32768; } // 16-bit operands outside bank 0 need to include the bank when computing // symbol adjustment. int operandForSymbol = operand; if (attr.OperandAddress >= 0) { operandForSymbol = attr.OperandAddress; } // 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) { // Special handling for the double-operand block move. string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, attr.DataDescriptor, operand >> 8, 1, PseudoOp.FormatNumericOpFlags.None); string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, attr.DataDescriptor, operand & 0xff, 1, PseudoOp.FormatNumericOpFlags.None); if (gen.Quirks.BlockMoveArgsReversed) { string tmp = opstr1; opstr1 = opstr2; opstr2 = tmp; } formattedOperand = opstr1 + "," + opstr2; } else { formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, attr.DataDescriptor, operandForSymbol, operandLen, opFlags); } } else { // Show operand value in hex. if (op.AddrMode == OpDef.AddressMode.BlockMove) { int arg1, arg2; if (gen.Quirks.BlockMoveArgsReversed) { arg1 = operand & 0xff; arg2 = operand >> 8; } else { arg1 = operand >> 8; arg2 = operand & 0xff; } formattedOperand = formatter.FormatHexValue(arg1, 2) + "," + formatter.FormatHexValue(arg2, 2); } else { if (operandLen == 2) { // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", // when outside bank zero. The bank is included in the operand address, // but we don't want to show it here. operandForSymbol &= 0xffff; } formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); } } string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); string eolComment = proj.Comments[offset]; if (doAddCycles) { bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, branchCross); if (cycles > 0) { eolComment = cycles.ToString() + " " + eolComment; } else { eolComment = (-cycles).ToString() + "+ " + eolComment; } } string commentStr = formatter.FormatEolComment(eolComment); string replMnemonic = gen.ModifyOpcode(offset, op); if (attr.Length != instrBytes) { // This instruction has another instruction inside it. Throw out what we // computed and just output as bytes. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap) { // Some assemblers have trouble generating PC-relative operands that wrap // around the bank. Output as raw hex. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (op.AddrMode == OpDef.AddressMode.BlockMove && gen.Quirks.BlockMoveArgsReversed) { // On second thought, just don't even output the wrong thing. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic == null) { // No mnemonic exists for this opcode. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic != string.Empty) { // A replacement mnemonic has been provided. opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis); } gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr); // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the // wrong thing if we're in emulation mode. Force flags back to short. if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm) { if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1) { gen.OutputRegWidthDirective(offset, 0, 0, 1, 1); } } }
/// <summary> /// Generates assembly source. /// /// This code is common to all generators. /// </summary> /// <param name="gen">Reference to generator object (presumably the caller).</param> /// <param name="sw">Text output sink.</param> /// <param name="worker">Background worker object, for progress updates and /// cancelation requests.</param> public static void Generate(IGenerator gen, StreamWriter sw, BackgroundWorker worker) { DisasmProject proj = gen.Project; Formatter formatter = gen.SourceFormatter; int offset = 0; bool doAddCycles = gen.Settings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); GenerateHeader(gen, sw); // Used for M/X flag tracking. StatusFlags prevFlags = StatusFlags.AllIndeterminate; int lastProgress = 0; while (offset < proj.FileData.Length) { Anattrib attr = proj.GetAnattrib(offset); if (attr.IsInstructionStart && offset > 0 && proj.GetAnattrib(offset - 1).IsData) { // Transition from data to code. (Don't add blank line for inline data.) gen.OutputLine(string.Empty); } // Long comments come first. if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) { List <string> formatted = longComment.FormatText(formatter, string.Empty); foreach (string str in formatted) { gen.OutputLine(str); } } // Check for address change. int orgAddr = proj.AddrMap.Get(offset); if (orgAddr >= 0) { gen.OutputOrgDirective(offset, orgAddr); } if (attr.IsInstructionStart) { // Generate M/X reg width directive, if necessary. // NOTE: we can suppress the initial directive if we know what the // target assembler's default assumption is. Probably want to handle // that in the ORG output handler. if (proj.CpuDef.HasEmuFlag) { StatusFlags curFlags = attr.StatusFlags; curFlags.M = attr.StatusFlags.ShortM ? 1 : 0; curFlags.X = attr.StatusFlags.ShortX ? 1 : 0; if (curFlags.M != prevFlags.M || curFlags.X != prevFlags.X) { // changed, output directive gen.OutputRegWidthDirective(offset, prevFlags.M, prevFlags.X, curFlags.M, curFlags.X); } prevFlags = curFlags; } // Look for embedded instructions. int len; for (len = 1; len < attr.Length; len++) { if (proj.GetAnattrib(offset + len).IsInstructionStart) { break; } } // Output instruction. GenerateInstruction(gen, sw, offset, len, doAddCycles); if (attr.DoesNotContinue) { gen.OutputLine(string.Empty); } offset += len; } else { gen.OutputDataOp(offset); offset += attr.Length; } // Update progress meter. We don't want to spam it, so just ping it 10x. int curProgress = (offset * 10) / proj.FileData.Length; if (lastProgress != curProgress) { if (worker.CancellationPending) { Debug.WriteLine("GenCommon got cancellation request"); return; } lastProgress = curProgress; worker.ReportProgress(curProgress * 10); //System.Threading.Thread.Sleep(500); } } }
// IGenerator public void OutputDataOp(int offset) { Formatter formatter = SourceFormatter; byte[] data = Project.FileData; Anattrib attr = Project.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = mLocalizer.ConvLabel(attr.Symbol.Label); } string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]); string opcodeStr, operandStr; FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); int length = dfd.Length; Debug.Assert(length > 0); bool multiLine = false; switch (dfd.FormatType) { case FormatDescriptor.Type.Default: if (length != 1) { Debug.Assert(false); length = 1; } opcodeStr = sDataOpNames.DefineData1; int operand = RawData.GetWord(data, offset, length, false); operandStr = formatter.FormatHexValue(operand, length * 2); break; case FormatDescriptor.Type.NumericLE: opcodeStr = sDataOpNames.GetDefineData(length); operand = RawData.GetWord(data, offset, length, false); if (length == 1 && dfd.IsStringOrCharacter && (operand & 0x7f) == '{' || (operand & 0x7f) == '}') { // Merlin32 can't handle "DFB '{'", so just output hex. operandStr = formatter.FormatHexValue(operand, length * 2); } else { operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); } break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); if ((string.IsNullOrEmpty(opcodeStr))) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); } break; case FormatDescriptor.Type.Fill: opcodeStr = sDataOpNames.Fill; if (data[offset] == 0) { operandStr = length.ToString(); } else { operandStr = length + "," + formatter.FormatHexValue(data[offset], 2); } break; case FormatDescriptor.Type.Dense: multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; case FormatDescriptor.Type.Junk: int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); if (fillVal >= 0) { opcodeStr = sDataOpNames.Fill; if (dfd.FormatSubType == FormatDescriptor.SubType.Align256 && GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) { // special syntax for page alignment if (fillVal == 0) { operandStr = "\\"; } else { operandStr = "\\," + formatter.FormatHexValue(fillVal, 2); } } else { if (fillVal == 0) { operandStr = length.ToString(); } else { operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); } } } else { // treat same as Dense multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); } break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: case FormatDescriptor.Type.StringL8: case FormatDescriptor.Type.StringL16: case FormatDescriptor.Type.StringDci: multiLine = true; opcodeStr = operandStr = null; OutputString(offset, labelStr, commentStr); break; default: opcodeStr = "???"; operandStr = "***"; break; } if (!multiLine) { opcodeStr = formatter.FormatPseudoOp(opcodeStr); OutputLine(labelStr, opcodeStr, operandStr, commentStr); } }
private void OutputString(int offset, string labelStr, string commentStr) { // This gets complicated. // // For Dci, L8String, and L16String, the entire string needs to fit in the // operand of one line. If it can't, we need to separate the length byte/word // or inverted character out, and just dump the rest as ASCII. Computing the // line length requires factoring delimiter character escapes. (NOTE: contrary // to the documentation, STR and STRL do include trailing hex characters in the // length calculation, so it's possible to escape delimiters.) // // For Reverse, we can span lines, but only if we emit the lines in // backward order. Also, Merlin doesn't allow hex to be embedded in a REV // operation, so we can't use REV if the string contains a delimiter. // // For aesthetic purposes, zero-length CString, L8String, and L16String // should be output as DFB/DW zeroes rather than an empty string -- makes // it easier to read. // // NOTE: we generally assume that the input is in the correct format, e.g. // the length byte in a StringL8 matches dfd.Length, and the high bits in DCI strings // have the right pattern. If not, we will generate bad output. This would need // to be scanned and corrected at a higher level. Anattrib attr = Project.GetAnattrib(offset); FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); Debug.Assert(dfd.IsString); Debug.Assert(dfd.Length > 0); // We can sort of do parts of C64 stuff, but it's probably more readable to just // output a commented blob than something where only the capital letters are readable. switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: case FormatDescriptor.SubType.HighAscii: break; case FormatDescriptor.SubType.C64Petscii: case FormatDescriptor.SubType.C64Screen: default: OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } Formatter formatter = SourceFormatter; byte[] data = Project.FileData; StringOpFormatter.ReverseMode revMode = StringOpFormatter.ReverseMode.Forward; int leadingBytes = 0; string opcodeStr; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: opcodeStr = sDataOpNames.StrGeneric; break; case FormatDescriptor.Type.StringReverse: opcodeStr = sDataOpNames.StrReverse; revMode = StringOpFormatter.ReverseMode.LineReverse; break; case FormatDescriptor.Type.StringNullTerm: opcodeStr = sDataOpNames.StrGeneric; // no pseudo-op for this if (dfd.Length == 1) { // Empty string. Just output the length byte(s) or null terminator. GenerateShortSequence(offset, 1, out string opcode, out string operand); OutputLine(labelStr, opcode, operand, commentStr); return; } break; case FormatDescriptor.Type.StringL8: opcodeStr = sDataOpNames.StrLen8; leadingBytes = 1; break; case FormatDescriptor.Type.StringL16: opcodeStr = sDataOpNames.StrLen16; leadingBytes = 2; break; case FormatDescriptor.Type.StringDci: opcodeStr = sDataOpNames.StrDci; break; default: Debug.Assert(false); return; } // Merlin 32 uses single-quote for low ASCII, double-quote for high ASCII. CharEncoding.Convert charConv; char delim; if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) { charConv = CharEncoding.ConvertHighAscii; delim = '"'; } else { charConv = CharEncoding.ConvertAscii; delim = '\''; } StringOpFormatter stropf = new StringOpFormatter(SourceFormatter, new Formatter.DelimiterDef(delim), StringOpFormatter.RawOutputStyle.DenseHex, MAX_OPERAND_LEN, charConv); if (dfd.FormatType == FormatDescriptor.Type.StringDci) { // DCI is awkward because the character encoding flips on the last byte. Rather // than clutter up StringOpFormatter for this rare item, we just accept low/high // throughout. stropf.CharConv = CharEncoding.ConvertLowAndHighAscii; } // Feed bytes in, skipping over the leading length bytes. stropf.FeedBytes(data, offset + leadingBytes, dfd.Length - leadingBytes, 0, revMode); Debug.Assert(stropf.Lines.Count > 0); // See if we need to do this over. bool redo = false; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringNullTerm: break; case FormatDescriptor.Type.StringReverse: if (stropf.HasEscapedText) { // can't include escaped characters in REV opcodeStr = sDataOpNames.StrGeneric; revMode = StringOpFormatter.ReverseMode.Forward; redo = true; } break; case FormatDescriptor.Type.StringL8: if (stropf.Lines.Count != 1) { // single-line only opcodeStr = sDataOpNames.StrGeneric; leadingBytes = 1; redo = true; } break; case FormatDescriptor.Type.StringL16: if (stropf.Lines.Count != 1) { // single-line only opcodeStr = sDataOpNames.StrGeneric; leadingBytes = 2; redo = true; } break; case FormatDescriptor.Type.StringDci: if (stropf.Lines.Count != 1) { // single-line only opcodeStr = sDataOpNames.StrGeneric; stropf.CharConv = charConv; redo = true; } break; default: Debug.Assert(false); return; } if (redo) { //Debug.WriteLine("REDO off=+" + offset.ToString("x6") + ": " + dfd.FormatType); // This time, instead of skipping over leading length bytes, we include them // explicitly. stropf.Reset(); stropf.FeedBytes(data, offset, dfd.Length, leadingBytes, revMode); } opcodeStr = formatter.FormatPseudoOp(opcodeStr); foreach (string str in stropf.Lines) { OutputLine(labelStr, opcodeStr, str, commentStr); labelStr = commentStr = string.Empty; // only show on first } }
private void OutputString(int offset, string labelStr, string commentStr) { // This gets complicated. // // For Dci, L8String, and L16String, the entire string needs to fit in the // operand of one line. If it can't, we need to separate the length byte/word // or inverted character out, and just dump the rest as ASCII. Computing the // line length requires factoring delimiter character escapes. (NOTE: contrary // to the documentation, STR and STRL do include trailing hex characters in the // length calculation, so it's possible to escape delimiters.) // // For Reverse, we can span lines, but only if we emit the lines in // backward order. Also, Merlin doesn't allow hex to be embedded in a REV // operation, so we can't use REV if the string contains a delimiter. // // DciReverse is deprecated, but we can handle it as a Reverse string with a // trailing byte on a following line. // // For aesthetic purposes, zero-length CString, L8String, and L16String // should be output as DFB/DW zeroes rather than an empty string -- makes // it easier to read. Formatter formatter = SourceFormatter; byte[] data = Project.FileData; Anattrib attr = Project.GetAnattrib(offset); FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); Debug.Assert(dfd.FormatType == FormatDescriptor.Type.String); Debug.Assert(dfd.Length > 0); bool highAscii = false; int showZeroes = 0; int leadingBytes = 0; int trailingBytes = 0; bool showLeading = false; bool showTrailing = false; RevMode revMode = RevMode.Forward; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: highAscii = (data[offset] & 0x80) != 0; break; case FormatDescriptor.SubType.Dci: highAscii = (data[offset] & 0x80) != 0; break; case FormatDescriptor.SubType.Reverse: highAscii = (data[offset] & 0x80) != 0; revMode = RevMode.Reverse; break; case FormatDescriptor.SubType.DciReverse: highAscii = (data[offset + dfd.Length - 1] & 0x80) != 0; revMode = RevMode.Reverse; break; case FormatDescriptor.SubType.CString: highAscii = (data[offset] & 0x80) != 0; if (dfd.Length == 1) { showZeroes = 1; // empty null-terminated string } trailingBytes = 1; showTrailing = true; break; case FormatDescriptor.SubType.L8String: if (dfd.Length > 1) { highAscii = (data[offset + 1] & 0x80) != 0; } else { //showZeroes = 1; } leadingBytes = 1; break; case FormatDescriptor.SubType.L16String: if (dfd.Length > 2) { highAscii = (data[offset + 2] & 0x80) != 0; } else { //showZeroes = 2; } leadingBytes = 2; break; default: Debug.Assert(false); return; } if (showZeroes != 0) { // Empty string. Just output the length byte(s) or null terminator. GenerateShortSequence(offset, showZeroes, out string opcode, out string operand); OutputLine(labelStr, opcode, operand, commentStr); return; } // Merlin 32 uses single-quote for low ASCII, double-quote for high ASCII. When // quoting the delimiter we use a hexadecimal value. We need to bear in mind that // we're forcing the characters to low ASCII, but the actual character being // escaped might be in high ASCII. Hence delim vs. delimReplace. char delim = highAscii ? '"' : '\''; char delimReplace = highAscii ? ((char)(delim | 0x80)) : delim; StringGather gath = null; // Run the string through so we can see if it'll fit on one line. As a minor // optimization, we skip this step for "generic" strings, which are probably // the most common thing. if (dfd.FormatSubType != FormatDescriptor.SubType.None) { gath = new StringGather(this, labelStr, "???", commentStr, delim, delimReplace, StringGather.ByteStyle.DenseHex, MAX_OPERAND_LEN, true); FeedGath(gath, data, offset, dfd.Length, revMode, leadingBytes, showLeading, trailingBytes, showTrailing); Debug.Assert(gath.NumLinesOutput > 0); } string opcodeStr; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; break; case FormatDescriptor.SubType.Dci: if (gath.NumLinesOutput == 1) { opcodeStr = highAscii ? sDataOpNames.StrDciHi : sDataOpNames.StrDci; } else { opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; trailingBytes = 1; showTrailing = true; } break; case FormatDescriptor.SubType.Reverse: if (gath.HasDelimiter) { // can't include escaped delimiters in REV opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; revMode = RevMode.Forward; } else if (gath.NumLinesOutput > 1) { opcodeStr = highAscii ? sDataOpNames.StrReverseHi : sDataOpNames.StrReverse; revMode = RevMode.BlockReverse; } else { opcodeStr = highAscii ? sDataOpNames.StrReverseHi : sDataOpNames.StrReverse; Debug.Assert(revMode == RevMode.Reverse); } break; case FormatDescriptor.SubType.DciReverse: // Mostly punt -- output as ASCII with special handling for first byte. opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; revMode = RevMode.Forward; leadingBytes = 1; showLeading = true; break; case FormatDescriptor.SubType.CString: //opcodeStr = sDataOpNames.StrNullTerm[highAscii ? 1 : 0]; opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; break; case FormatDescriptor.SubType.L8String: if (gath.NumLinesOutput == 1) { opcodeStr = highAscii ? sDataOpNames.StrLen8Hi : sDataOpNames.StrLen8; } else { opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; leadingBytes = 1; showLeading = true; } break; case FormatDescriptor.SubType.L16String: if (gath.NumLinesOutput == 1) { opcodeStr = highAscii ? sDataOpNames.StrLen16Hi : sDataOpNames.StrLen16; } else { opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; leadingBytes = 2; showLeading = true; } break; default: Debug.Assert(false); return; } opcodeStr = formatter.FormatPseudoOp(opcodeStr); // Create a new StringGather, with the final opcode choice. gath = new StringGather(this, labelStr, opcodeStr, commentStr, delim, delimReplace, StringGather.ByteStyle.DenseHex, MAX_OPERAND_LEN, false); FeedGath(gath, data, offset, dfd.Length, revMode, leadingBytes, showLeading, trailingBytes, showTrailing); }
// IGenerator public void OutputDataOp(int offset) { Formatter formatter = SourceFormatter; byte[] data = Project.FileData; Anattrib attr = Project.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = mLocalizer.ConvLabel(attr.Symbol.Label); } string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]); string opcodeStr, operandStr; FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); int length = dfd.Length; Debug.Assert(length > 0); bool multiLine = false; switch (dfd.FormatType) { case FormatDescriptor.Type.Default: if (length != 1) { Debug.Assert(false); length = 1; } opcodeStr = sDataOpNames.DefineData1; int operand = RawData.GetWord(data, offset, length, false); operandStr = formatter.FormatHexValue(operand, length * 2); break; case FormatDescriptor.Type.NumericLE: opcodeStr = sDataOpNames.GetDefineData(length); operand = RawData.GetWord(data, offset, length, false); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.None); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); if (opcodeStr == null) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.None); } break; case FormatDescriptor.Type.Fill: opcodeStr = sDataOpNames.Fill; operandStr = length + "," + formatter.FormatHexValue(data[offset], 2); break; case FormatDescriptor.Type.Dense: multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; case FormatDescriptor.Type.String: multiLine = true; opcodeStr = operandStr = null; OutputString(offset, labelStr, commentStr); break; default: opcodeStr = "???"; operandStr = "***"; break; } if (!multiLine) { opcodeStr = formatter.FormatPseudoOp(opcodeStr); OutputLine(labelStr, opcodeStr, operandStr, commentStr); } }
private void OutputString(int offset, string labelStr, string commentStr) { // Generic strings whose encoding matches the configured text encoding are output // with a simple .text directive. // // CString and L8String have directives (.null, .ptext), but we can only use // them if the string fits on one line and doesn't include delimiters. // // We might be able to define a macro for Reverse. // // We don't currently switch character encodings in the middle of a file. We could // do so to flip between PETSCII, screen codes, low ASCII, and high ASCII, but it // adds a lot of noise and it's unclear that this is generally useful. Anattrib attr = Project.GetAnattrib(offset); FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); Debug.Assert(dfd.IsString); Debug.Assert(dfd.Length > 0); CharEncoding.Convert charConv = null; CharEncoding.Convert dciConv = null; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: charConv = CharEncoding.ConvertAscii; dciConv = CharEncoding.ConvertLowAndHighAscii; break; case FormatDescriptor.SubType.HighAscii: charConv = CharEncoding.ConvertHighAscii; dciConv = CharEncoding.ConvertLowAndHighAscii; break; case FormatDescriptor.SubType.C64Petscii: charConv = CharEncoding.ConvertC64Petscii; dciConv = CharEncoding.ConvertLowAndHighC64Petscii; break; case FormatDescriptor.SubType.C64Screen: charConv = CharEncoding.ConvertC64ScreenCode; dciConv = CharEncoding.ConvertLowAndHighC64ScreenCode; break; default: break; } if (charConv == null) { OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } // Issue a .enc, if needed. UpdateCharacterEncoding(dfd); Formatter formatter = SourceFormatter; byte[] data = Project.FileData; int hiddenLeadingBytes = 0; int shownLeadingBytes = 0; int trailingBytes = 0; string opcodeStr; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: opcodeStr = sDataOpNames.StrGeneric; break; case FormatDescriptor.Type.StringNullTerm: opcodeStr = sDataOpNames.StrNullTerm; trailingBytes = 1; break; case FormatDescriptor.Type.StringL8: opcodeStr = sDataOpNames.StrLen8; hiddenLeadingBytes = 1; break; case FormatDescriptor.Type.StringL16: opcodeStr = sDataOpNames.StrGeneric; shownLeadingBytes = 2; break; case FormatDescriptor.Type.StringDci: opcodeStr = sDataOpNames.StrDci; if ((Project.FileData[offset] & 0x80) != 0) { // ".shift" directive only works for strings where the low bit starts // clear and ends high. OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } break; default: Debug.Assert(false); return; } StringOpFormatter stropf = new StringOpFormatter(SourceFormatter, Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, MAX_OPERAND_LEN, charConv); if (dfd.FormatType == FormatDescriptor.Type.StringDci) { // DCI is awkward because the character encoding flips on the last byte. Rather // than clutter up StringOpFormatter for this rare item, we just accept low/high // throughout. stropf.CharConv = dciConv; } // Feed bytes in, skipping over hidden bytes (leading L8, trailing null). stropf.FeedBytes(data, offset + hiddenLeadingBytes, dfd.Length - hiddenLeadingBytes - trailingBytes, shownLeadingBytes, StringOpFormatter.ReverseMode.Forward); Debug.Assert(stropf.Lines.Count > 0); // See if we need to do this over. bool redo = false; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringL16: // All good the first time. break; case FormatDescriptor.Type.StringNullTerm: case FormatDescriptor.Type.StringL8: case FormatDescriptor.Type.StringDci: if (stropf.Lines.Count != 1) { // Must be single-line. opcodeStr = sDataOpNames.StrGeneric; stropf.CharConv = charConv; // undo DCI hack redo = true; } break; default: Debug.Assert(false); return; } if (redo) { //Debug.WriteLine("REDO off=+" + offset.ToString("x6") + ": " + dfd.FormatType); // This time, instead of skipping over leading length bytes, we include them // explicitly. stropf.Reset(); stropf.FeedBytes(data, offset, dfd.Length, hiddenLeadingBytes, StringOpFormatter.ReverseMode.Forward); } opcodeStr = formatter.FormatPseudoOp(opcodeStr); foreach (string str in stropf.Lines) { OutputLine(labelStr, opcodeStr, str, commentStr); labelStr = commentStr = string.Empty; // only show on first } }
// IGenerator public void OutputDataOp(int offset) { Formatter formatter = SourceFormatter; byte[] data = Project.FileData; Anattrib attr = Project.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = mLocalizer.ConvLabel(attr.Symbol.Label); } string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]); string opcodeStr, operandStr; FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); int length = dfd.Length; Debug.Assert(length > 0); bool multiLine = false; switch (dfd.FormatType) { case FormatDescriptor.Type.Default: if (length != 1) { Debug.Assert(false); length = 1; } opcodeStr = sDataOpNames.DefineData1; int operand = RawData.GetWord(data, offset, length, false); operandStr = formatter.FormatHexValue(operand, length * 2); break; case FormatDescriptor.Type.NumericLE: opcodeStr = sDataOpNames.GetDefineData(length); operand = RawData.GetWord(data, offset, length, false); UpdateCharacterEncoding(dfd); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.None); break; case FormatDescriptor.Type.NumericBE: opcodeStr = sDataOpNames.GetDefineBigData(length); if ((string.IsNullOrEmpty(opcodeStr))) { // Nothing defined, output as comma-separated single-byte values. GenerateShortSequence(offset, length, out opcodeStr, out operandStr); } else { UpdateCharacterEncoding(dfd); operand = RawData.GetWord(data, offset, length, true); operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, mLocalizer.LabelMap, dfd, operand, length, PseudoOp.FormatNumericOpFlags.None); } break; case FormatDescriptor.Type.Fill: opcodeStr = sDataOpNames.Fill; operandStr = length + "," + formatter.FormatHexValue(data[offset], 2); break; case FormatDescriptor.Type.Dense: multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; case FormatDescriptor.Type.Junk: int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); if (fillVal >= 0 && GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) { // .align <expression>[, <fill>] opcodeStr = sDataOpNames.Align; int alignVal = 1 << FormatDescriptor.AlignmentToPower(dfd.FormatSubType); operandStr = alignVal.ToString() + "," + formatter.FormatHexValue(fillVal, 2); } else if (fillVal >= 0) { // treat same as Fill opcodeStr = sDataOpNames.Fill; operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); } else { // treat same as Dense multiLine = true; opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); } break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: case FormatDescriptor.Type.StringL8: case FormatDescriptor.Type.StringL16: case FormatDescriptor.Type.StringDci: multiLine = true; opcodeStr = operandStr = null; OutputString(offset, labelStr, commentStr); break; default: opcodeStr = "???"; operandStr = "***"; break; } if (!multiLine) { opcodeStr = formatter.FormatPseudoOp(opcodeStr); OutputLine(labelStr, opcodeStr, operandStr, commentStr); } }
private void OutputString(int offset, string labelStr, string commentStr) { Formatter formatter = SourceFormatter; byte[] data = Project.FileData; Anattrib attr = Project.GetAnattrib(offset); FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); Debug.Assert(dfd.IsString); Debug.Assert(dfd.Length > 0); string opcodeStr; CharEncoding.Convert charConv; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: opcodeStr = sDataOpNames.StrGeneric; charConv = CharEncoding.ConvertAscii; break; case FormatDescriptor.SubType.HighAscii: opcodeStr = sDataOpNames.StrGeneric; charConv = CharEncoding.ConvertHighAscii; break; case FormatDescriptor.SubType.C64Petscii: opcodeStr = "!pet"; charConv = CharEncoding.ConvertC64Petscii; break; case FormatDescriptor.SubType.C64Screen: opcodeStr = "!scr"; charConv = CharEncoding.ConvertC64ScreenCode; break; default: Debug.Assert(false); OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } int leadingBytes = 0; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: case FormatDescriptor.Type.StringDci: // Last byte may be output as hex. break; case FormatDescriptor.Type.StringL8: // Length byte will be output as hex. leadingBytes = 1; break; case FormatDescriptor.Type.StringL16: // Length byte will be output as hex. leadingBytes = 2; break; default: Debug.Assert(false); return; } StringOpFormatter stropf = new StringOpFormatter(SourceFormatter, Formatter.DOUBLE_QUOTE_DELIM, StringOpFormatter.RawOutputStyle.CommaSep, MAX_OPERAND_LEN, charConv); stropf.FeedBytes(data, offset, dfd.Length, leadingBytes, StringOpFormatter.ReverseMode.Forward); if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii && stropf.HasEscapedText) { // Can't !xor the output, because while it works for string data it // also flips the high bits on the unprintable bytes we output as raw hex. OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) { OutputLine(string.Empty, "!xor", "$80 {", string.Empty); } foreach (string str in stropf.Lines) { OutputLine(labelStr, opcodeStr, str, commentStr); labelStr = commentStr = string.Empty; // only show on first } if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) { OutputLine(string.Empty, "}", string.Empty, string.Empty); } }