/// <summary> /// Feeds the bytes into the StringGather. /// </summary> private void FeedGath(StringGather gath, byte[] data, int offset, int length, int leadingBytes, bool showLeading, int trailingBytes, bool showTrailing) { int startOffset = offset; int strEndOffset = offset + length - trailingBytes; if (showLeading) { while (leadingBytes-- > 0) { gath.WriteByte(data[offset++]); } } else { offset += leadingBytes; } for (; offset < strEndOffset; offset++) { gath.WriteChar((char)(data[offset] & 0x7f)); } while (showTrailing && trailingBytes-- > 0) { gath.WriteByte(data[offset++]); } gath.Finish(); }
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 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 leadingBytes = 0; int trailingBytes = 0; bool showLeading = false; bool showTrailing = false; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: highAscii = (data[offset] & 0x80) != 0; break; case FormatDescriptor.SubType.Dci: highAscii = (data[offset] & 0x80) != 0; trailingBytes = 1; showTrailing = true; break; case FormatDescriptor.SubType.Reverse: highAscii = (data[offset] & 0x80) != 0; break; case FormatDescriptor.SubType.DciReverse: highAscii = (data[offset + dfd.Length - 1] & 0x80) != 0; leadingBytes = 1; showLeading = true; break; case FormatDescriptor.SubType.CString: highAscii = (data[offset] & 0x80) != 0; trailingBytes = 1; showTrailing = true; break; case FormatDescriptor.SubType.L8String: if (dfd.Length > 1) { highAscii = (data[offset + 1] & 0x80) != 0; } leadingBytes = 1; showLeading = true; break; case FormatDescriptor.SubType.L16String: if (dfd.Length > 2) { highAscii = (data[offset + 2] & 0x80) != 0; } leadingBytes = 2; showLeading = true; break; default: Debug.Assert(false); return; } char 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 || highAscii) { gath = new StringGather(this, labelStr, "???", commentStr, delim, delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, true); FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading, trailingBytes, showTrailing); Debug.Assert(gath.NumLinesOutput > 0); } string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.StrGeneric); switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: // Special case for simple short high-ASCII strings. These have no // leading or trailing bytes. We can improve this a bit by handling // arbitrarily long strings by simply breaking them across lines. Debug.Assert(leadingBytes == 0); Debug.Assert(trailingBytes == 0); if (highAscii && gath.NumLinesOutput == 1 && !gath.HasDelimiter) { if (!mHighAsciiMacroOutput) { mHighAsciiMacroOutput = true; // Output a macro for high-ASCII strings. 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"); highAscii = false; } break; case FormatDescriptor.SubType.Dci: case FormatDescriptor.SubType.Reverse: case FormatDescriptor.SubType.DciReverse: // Full configured above. break; case FormatDescriptor.SubType.CString: if (gath.NumLinesOutput == 1 && !gath.HasDelimiter) { opcodeStr = sDataOpNames.StrNullTerm; showTrailing = false; } break; case FormatDescriptor.SubType.L8String: case FormatDescriptor.SubType.L16String: // Implement macros? break; default: Debug.Assert(false); return; } if (highAscii) { OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } // Create a new StringGather, with the final opcode choice. gath = new StringGather(this, labelStr, opcodeStr, commentStr, delim, delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, false); FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading, trailingBytes, showTrailing); }
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); }
/// <summary> /// Feeds the bytes into the StringGather. /// </summary> private void FeedGath(StringGather gath, byte[] data, int offset, int length, RevMode revMode, int leadingBytes, bool showLeading, int trailingBytes, bool showTrailing) { int startOffset = offset; int strEndOffset = offset + length - trailingBytes; if (showLeading) { while (leadingBytes-- > 0) { gath.WriteByte(data[offset++]); } } else { offset += leadingBytes; } if (revMode == RevMode.BlockReverse) { const int maxPerLine = MAX_OPERAND_LEN - 2; int numBlockLines = (length + maxPerLine - 1) / maxPerLine; for (int chunk = 0; chunk < numBlockLines; chunk++) { int chunkOffset = startOffset + chunk * maxPerLine; int endOffset = chunkOffset + maxPerLine; if (endOffset > strEndOffset) { endOffset = strEndOffset; } for (int off = endOffset - 1; off >= chunkOffset; off--) { gath.WriteChar((char)(data[off] & 0x7f)); } } } else { for (; offset < strEndOffset; offset++) { if (revMode == RevMode.Forward) { gath.WriteChar((char)(data[offset] & 0x7f)); } else if (revMode == RevMode.Reverse) { int posn = startOffset + (strEndOffset - offset) - 1; gath.WriteChar((char)(data[posn] & 0x7f)); } else { Debug.Assert(false); } } } while (showTrailing && trailingBytes-- > 0) { gath.WriteByte(data[offset++]); } gath.Finish(); }
private void OutputString(int offset, string labelStr, string commentStr) { // Normal ASCII strings are handled 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 could probably do something fancy with the character encoding options to // make high-ASCII work nicely. // // We might be able to define a macro for DCI and Reverse. 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 leadingBytes = 0; int trailingBytes = 0; bool showLeading = false; bool showTrailing = false; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: highAscii = (data[offset] & 0x80) != 0; break; case FormatDescriptor.SubType.Dci: highAscii = (data[offset] & 0x80) != 0; trailingBytes = 1; showTrailing = true; break; case FormatDescriptor.SubType.Reverse: highAscii = (data[offset] & 0x80) != 0; break; case FormatDescriptor.SubType.DciReverse: highAscii = (data[offset + dfd.Length - 1] & 0x80) != 0; leadingBytes = 1; showLeading = true; break; case FormatDescriptor.SubType.CString: highAscii = (data[offset] & 0x80) != 0; trailingBytes = 1; showTrailing = true; break; case FormatDescriptor.SubType.L8String: if (dfd.Length > 1) { highAscii = (data[offset + 1] & 0x80) != 0; } leadingBytes = 1; showLeading = true; break; case FormatDescriptor.SubType.L16String: if (dfd.Length > 2) { highAscii = (data[offset + 2] & 0x80) != 0; } leadingBytes = 2; showLeading = true; break; default: Debug.Assert(false); return; } char 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 || highAscii) { gath = new StringGather(this, labelStr, "???", commentStr, delim, delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, true); FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading, trailingBytes, showTrailing); Debug.Assert(gath.NumLinesOutput > 0); } string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.StrGeneric); switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: // TODO(someday): something fancy with encodings to handle high-ASCII text? break; case FormatDescriptor.SubType.Dci: case FormatDescriptor.SubType.Reverse: case FormatDescriptor.SubType.DciReverse: // Fully configured above. break; case FormatDescriptor.SubType.CString: if (gath.NumLinesOutput == 1 && !gath.HasDelimiter) { opcodeStr = sDataOpNames.StrNullTerm; showTrailing = false; } break; case FormatDescriptor.SubType.L8String: if (gath.NumLinesOutput == 1 && !gath.HasDelimiter) { opcodeStr = sDataOpNames.StrLen8; showLeading = false; } break; case FormatDescriptor.SubType.L16String: // Implement as macro? break; default: Debug.Assert(false); return; } if (highAscii) { OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } // Create a new StringGather, with the final opcode choice. gath = new StringGather(this, labelStr, opcodeStr, commentStr, delim, delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, false); FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading, trailingBytes, showTrailing); }