/* * 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 { /// /// 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). /// 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; /// /// 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; /// /// Next available character position. /// private int mIndex = 0; /// /// State of the buffer, based on the last thing we added. /// private enum State { Unknown = 0, StartOfLine, InQuote, OutQuote } private State mState = State.StartOfLine; /// /// Constructor. /// /// Reference back to generator, for output function and /// format options. /// Line label. Appears on first output line only. /// Opcode to use for all lines. /// End-of-line comment. Appears on first output line /// only. /// String delimiter character. /// If true, no file output is produced. 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; } /// /// Write a character into the buffer. /// /// Character to add. 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; } /// /// Write a hex value into the buffer. /// /// Value to add. 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; } /// /// Tells the object to flush any pending data to the output. /// public void Finish() { Flush(); } /// /// Outputs the buffer of pending data. A closing delimiter will be added if needed. /// 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; } } }