Code/Wasm/Binary/BinaryWasmWriter.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace WasmBox.Wasm.Binary {
    /// <summary>
    /// A writes that writes the binary WebAssembly format.
    /// </summary>
    public class BinaryWasmWriter {
		/// <summary>
		/// Initializes a new instance of the <see cref="BinaryWasmWriter"/> class.
		/// </summary>
		/// <param name="writer">The binary writer for a WebAssembly file.</param>
		public BinaryWasmWriter(BinaryWriter writer)
            : this(writer, UTF8Encoding.UTF8) { }

		/// <summary>
		/// Initializes a new instance of the <see cref="BinaryWasmWriter"/> class.
		/// </summary>
		/// <param name="writer">The binary writer for a WebAssembly file.</param>
		/// <param name="stringEncoding">The encoding for strings in the WebAssembly file.</param>
		public BinaryWasmWriter(BinaryWriter writer, Encoding stringEncoding) {
            this.Writer = writer;
            this.StringEncoding = stringEncoding;
        }

        /// <summary>
        /// The binary writer for a WebAssembly file.
        /// </summary>
        public BinaryWriter Writer { get; private set; }

        /// <summary>
        /// The encoding that is used to write strings.
        /// </summary>
        /// <returns>The string encoding.</returns>
        public Encoding StringEncoding { get; private set; }

        /// <summary>
        /// Writes an unsigned LEB128 variable-length integer, limited to 64 bits.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarUInt64(ulong value) {
            // C# translation of code borrowed from Wikipedia article:
            // https://en.wikipedia.org/wiki/LEB128

            int count = 0;
            do {
                byte b = (byte)(value & 0x7F);
                value >>= 7;
                if (value != 0)
                    b |= 0x80;

                Writer.Write(b);
                count++;
            } while (value != 0);
            return count;
        }

        /// <summary>
        /// Writes an unsigned LEB128 variable-length integer, limited to 32 bits.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarUInt32(uint value) {
            return WriteVarUInt64(value);
        }

        /// <summary>
        /// Writes an unsigned LEB128 variable-length integer, limited to 7 bits.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarUInt7(byte value) {
            return WriteVarUInt32(value);
        }

        /// <summary>
        /// Writes an unsigned LEB128 variable-length integer, limited to one bit.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarUInt1(bool value) {
            return WriteVarUInt32(value ? 1u : 0u);
        }

        /// <summary>
        /// Writes a signed LEB128 variable-length integer, limited to 64 bits.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarInt64(long value) {
            // C# translation of code borrowed from Wikipedia article:
            // https://en.wikipedia.org/wiki/LEB128

            int count = 0;
            bool more = true;
            while (more) {
                byte b = (byte)(value & 0x7F);
                value >>= 7;

                if ((value == 0 && ((b & 0x40) == 0)) || (value == -1 && ((b & 0x40) == 0x40)))
                    more = false;
                else
                    // set high order bit of byte
                    b |= 0x80;

                Writer.Write(b);
                count++;
            }
            return count;
        }

        /// <summary>
        /// Writes a signed LEB128 variable-length integer, limited to 32 bits.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarInt32(int value) {
            return WriteVarInt64(value);
        }

        /// <summary>
        /// Writes a signed LEB128 variable-length integer, limited to 7 bits.
        /// </summary>
        /// <returns>The number of bytes used to encode the integer.</returns>
        public int WriteVarInt7(sbyte value) {
            return WriteVarInt64(value);
        }

        /// <summary>
        /// Writes a 32-bit floating-point number.
        /// </summary>
        /// <param name="value">The floating-point number to write.</param>
        /// <returns>The number of bytes used to encode the floating-point number.</returns>
        public int WriteFloat32(float value) {
            Writer.Write(value);
            return 4;
        }

        /// <summary>
        /// Writes a 64-bit floating-point number.
        /// </summary>
        /// <param name="value">The floating-point number to write.</param>
        /// <returns>The number of bytes used to encode the floating-point number.</returns>
        public int WriteFloat64(double value) {
            Writer.Write(value);
            return 8;
        }

        /// <summary>
        /// Writes a WebAssembly language type.
        /// </summary>
        /// <param name="value">The WebAssembly language type to write.</param>
        /// <returns>The number of bytes used to encode the type.</returns>
        public int WriteWasmType(WasmType value) {
            return WriteVarInt7((sbyte)value);
        }

        /// <summary>
        /// Writes a WebAssembly value type.
        /// </summary>
        /// <param name="value">The WebAssembly language value to write.</param>
        /// <returns>The number of bytes used to encode the type.</returns>
        public int WriteWasmValueType(WasmValueType value) {
            return WriteVarInt7((sbyte)value);
        }

        /// <summary>
        /// Writes a length-prefixed string to the WebAssembly file.
        /// </summary>
        /// <param name="value">The string to write to the file.</param>
        public void WriteString(string value) {
            byte[] buffer = StringEncoding.GetBytes(value);
            WriteVarUInt32((uint)buffer.Length);
            Writer.Write(buffer);
        }

        /// <summary>
        /// Writes data and prefixes it with a variable-length 32-bit unsigned integer
        /// that specifies the number of bytes written.
        /// </summary>
        /// <param name="writeData">Writes data to a WebAssembly file.</param>
        public void WriteLengthPrefixed(Action<BinaryWasmWriter> writeData) {
            using (var memStream = new MemoryStream()) {
                var innerWriter = new BinaryWasmWriter(
                    new BinaryWriter(memStream),
                    StringEncoding);

                // Write the contents to the memory stream.
                writeData(innerWriter);

                // Save the number of bytes we've written.
                var numberOfBytes = memStream.Position;

                // Seek to the beginning of the memory stream.
                memStream.Seek(0, SeekOrigin.Begin);

                // Write the size of the contents to follow, in bytes.
                WriteVarUInt32((uint)numberOfBytes);

                // Write the memory stream's data to the writer's stream.
                Writer.Write(memStream.GetBuffer(), 0, (int)numberOfBytes);
            }
        }

        /// <summary>
        /// Writes a WebAssembly version header.
        /// </summary>
        /// <param name="header">The WebAssembly version header to write.</param>
        public void WriteVersionHeader(VersionHeader header) {
            Writer.Write(header.Magic);
            Writer.Write(header.Version);
        }

        /// <summary>
        /// Writes a WebAssembly section, including its header.
        /// </summary>
        /// <param name="value">The WebAssembly section to write.</param>
        public void WriteSection(Section value) {
            WriteVarInt7((sbyte)value.Name.Code);
            WriteLengthPrefixed(value.WriteCustomNameAndPayloadTo);
        }

        /// <summary>
        /// Writes a WebAssembly file.
        /// </summary>
        /// <param name="file">The WebAssembly file to write.</param>
        public void WriteFile(WasmFile file) {
            WriteVersionHeader(file.Header);
            foreach (var section in file.Sections) {
                WriteSection(section);
            }
        }
    }
}