1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-07-04 01:29:34 +00:00
6502bench/SourceGenWPF/AsmGen/StringGather.cs
Andy McFadden 17af7efbbb Show formatted data in the code list
The disassembled lines are now shown in the custom-styled list view.
The DisplayList isn't being kept up to date on edits, but since we
can't edit anything yet that's not too limiting.

Pulled more code over, including the mostly-GUI-agnostic bits of the
source generation and assembler execution code.
2019-05-27 18:52:25 -07:00

228 lines
8.6 KiB
C#

/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Diagnostics;
namespace SourceGenWPF.AsmGen {
/// <summary>
/// Multi-line string gatherer. Accumulates characters and raw bytes, emitting
/// them when we have a full operand's worth.
///
/// If the delimiter character appears, it will be output inline as a raw byte.
/// The low-ASCII string ['hello'world'] will become [27,'hello',27,'world',27]
/// (or something similar).
/// </summary>
public class StringGather {
// Inputs.
public IGenerator Gen { get; private set; }
public string Label { get; private set; }
public string Opcode { get; private set; }
public string Comment { get; private set; }
public char Delimiter { get; private set; }
public char DelimiterReplacement { get; private set; }
public ByteStyle ByteStyleX { get; private set; }
public int MaxOperandLen { get; private set; }
public bool IsTestRun { get; private set; }
public enum ByteStyle { DenseHex, CommaSep };
// Outputs.
public bool HasDelimiter { get; private set; }
public int NumLinesOutput { get; private set; }
private char[] mHexChars;
/// <summary>
/// Character collection buffer. The delimiters are written into the buffer
/// because they're mixed with bytes, particularly when we have to escape the
/// delimiter character. Strings might start or end with escaped delimiters,
/// so we don't add them until we have to.
private char[] mBuffer;
/// <summary>
/// Next available character position.
/// </summary>
private int mIndex = 0;
/// <summary>
/// State of the buffer, based on the last thing we added.
/// </summary>
private enum State {
Unknown = 0,
StartOfLine,
InQuote,
OutQuote
}
private State mState = State.StartOfLine;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="gen">Reference back to generator, for output function and
/// format options.</param>
/// <param name="label">Line label. Appears on first output line only.</param>
/// <param name="opcode">Opcode to use for all lines.</param>
/// <param name="comment">End-of-line comment. Appears on first output line
/// only.</param>
/// <param name="delimiter">String delimiter character.</param>
/// <param name="isTestRun">If true, no file output is produced.</param>
public StringGather(IGenerator gen, string label, string opcode,
string comment, char delimiter, char delimReplace, ByteStyle byteStyle,
int maxOperandLen, bool isTestRun) {
Gen = gen;
Label = label;
Opcode = opcode;
Comment = comment;
Delimiter = delimiter;
DelimiterReplacement = delimReplace;
ByteStyleX = byteStyle;
MaxOperandLen = maxOperandLen;
IsTestRun = isTestRun;
mBuffer = new char[MaxOperandLen];
mHexChars = Gen.SourceFormatter.HexDigits;
}
/// <summary>
/// Write a character into the buffer.
/// </summary>
/// <param name="ch">Character to add.</param>
public void WriteChar(char ch) {
Debug.Assert(ch >= 0 && ch <= 0xff);
if (ch == Delimiter) {
// Must write it as a byte.
HasDelimiter = true;
WriteByte((byte)DelimiterReplacement);
return;
}
// If we're at the start of a line, add delimiter, then new char.
// If we're inside quotes, just add the character. We must have space for
// two chars (new char, close quote).
// If we're outside quotes, add a comma and delimiter, then the character.
// We must have 4 chars remaining (comma, open quote, new char, close quote).
switch (mState) {
case State.StartOfLine:
mBuffer[mIndex++] = Delimiter;
break;
case State.InQuote:
if (mIndex + 2 > MaxOperandLen) {
Flush();
mBuffer[mIndex++] = Delimiter;
}
break;
case State.OutQuote:
if (mIndex + 4 > MaxOperandLen) {
Flush();
mBuffer[mIndex++] = Delimiter;
} else {
mBuffer[mIndex++] = ',';
mBuffer[mIndex++] = Delimiter;
}
break;
default:
Debug.Assert(false);
break;
}
mBuffer[mIndex++] = ch;
mState = State.InQuote;
}
/// <summary>
/// Write a hex value into the buffer.
/// </summary>
/// <param name="val">Value to add.</param>
public void WriteByte(byte val) {
// If we're at the start of a line, just output the byte.
// If we're inside quotes, emit a delimiter, comma, and the byte. We must
// have space for four (DenseHex) or five (CommaSep) chars.
// If we're outside quotes, add the byte. We must have two (DenseHex) or
// four (CommaSep) chars remaining.
switch (mState) {
case State.StartOfLine:
break;
case State.InQuote:
int minWidth = (ByteStyleX == ByteStyle.CommaSep) ? 5 : 4;
if (mIndex + minWidth > MaxOperandLen) {
Flush();
} else {
mBuffer[mIndex++] = Delimiter;
mBuffer[mIndex++] = ',';
}
break;
case State.OutQuote:
minWidth = (ByteStyleX == ByteStyle.CommaSep) ? 4 : 2;
if (mIndex + minWidth > MaxOperandLen) {
Flush();
} else {
if (ByteStyleX == ByteStyle.CommaSep) {
mBuffer[mIndex++] = ',';
}
}
break;
default:
Debug.Assert(false);
break;
}
if (ByteStyleX == ByteStyle.CommaSep) {
mBuffer[mIndex++] = '$';
}
mBuffer[mIndex++] = mHexChars[val >> 4];
mBuffer[mIndex++] = mHexChars[val & 0x0f];
mState = State.OutQuote;
}
/// <summary>
/// Tells the object to flush any pending data to the output.
/// </summary>
public void Finish() {
Flush();
}
/// <summary>
/// Outputs the buffer of pending data. A closing delimiter will be added if needed.
/// </summary>
private void Flush() {
switch (mState) {
case State.StartOfLine:
// empty string; put out a pair of delimiters
mBuffer[mIndex++] = Delimiter;
mBuffer[mIndex++] = Delimiter;
NumLinesOutput++;
break;
case State.InQuote:
// add delimiter and finish
mBuffer[mIndex++] = Delimiter;
NumLinesOutput++;
break;
case State.OutQuote:
// just output it
NumLinesOutput++;
break;
}
if (!IsTestRun) {
Gen.OutputLine(Label, Opcode, new string(mBuffer, 0, mIndex),
Comment);
}
mIndex = 0;
// Erase these after first use so we don't put them on every line.
Label = Comment = string.Empty;
}
}
}