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, 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) { // 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 } }
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); 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 } }