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

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

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

		/// <summary>
		/// Initializes a new instance of the <see cref="BinaryWasmReader"/> class.
		/// </summary>
		/// <param name="reader">The binary reader for a WebAssembly file.</param>
		/// <param name="stringEncoding">The encoding for strings in the WebAssembly file.</param>
		/// <param name="streamIsEmpty">Tests if the stream is empty.</param>
		public BinaryWasmReader(
            BinaryReader reader,
            Encoding stringEncoding,
            Func<bool> streamIsEmpty) {
            this.reader = reader;
            this.StringEncoding = stringEncoding;
            this.streamIsEmpty = streamIsEmpty;
        }

		/// <summary>
		/// Initializes a new instance of the <see cref="BinaryWasmReader"/> class.
		/// </summary>
		/// <param name="reader">The binary reader for a WebAssembly file.</param>
		/// <param name="streamIsEmpty">Tests if the stream is empty.</param>
		public BinaryWasmReader(
            BinaryReader reader,
            Func<bool> streamIsEmpty) {
            this.reader = reader;
            this.StringEncoding = UTF8Encoding.UTF8;
            this.streamIsEmpty = streamIsEmpty;
        }

        /// <summary>
        /// The binary reader for a WebAssembly file.
        /// </summary>
        private BinaryReader reader;

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

        /// <summary>
        /// Tests if the stream is empty.
        /// </summary>
        private Func<bool> streamIsEmpty;

        /// <summary>
        /// A default implementation that tests if the stream is empty.
        /// </summary>
        private bool defaultStreamIsEmptyImpl() {
            return Position >= reader.BaseStream.Length;
        }

        /// <summary>
        /// Gets the current position of the reader in the WebAssembly file.
        /// </summary>
        public long Position { get; private set; }

        /// <summary>
        /// Reads a single byte.
        /// </summary>
        /// <returns>The byte that was read.</returns>
        public byte ReadByte() {
            byte result = reader.ReadByte();
            Position++;
            return result;
        }

        /// <summary>
        /// Reads a range of bytes.
        /// </summary>
        /// <param name="count">The number of bytes to read.</param>
        /// <returns>The array of bytes that were read.</returns>
        public byte[] ReadBytes(int count) {
            byte[] results = reader.ReadBytes(count);
            Position += count;
            return results;
        }

        /// <summary>
        /// Parses an unsigned LEB128 variable-length integer, limited to 64 bits.
        /// </summary>
        /// <returns>The parsed unsigned 64-bit integer.</returns>
        public ulong ReadVarUInt64() {
            // C# translation of code borrowed from Wikipedia article:
            // https://en.wikipedia.org/wiki/LEB128
            ulong result = 0;
            int shift = 0;
            while (true) {
                byte b = ReadByte();
                result |= ((ulong)(b & 0x7F) << shift);
                if ((b & 0x80) == 0)
                    break;
                shift += 7;
            }
            return result;
        }

        /// <summary>
        /// Parses an unsigned LEB128 variable-length integer, limited to one bit.
        /// </summary>
        /// <returns>The parsed unsigned 1-bit integer, as a Boolean.</returns>
        public bool ReadVarUInt1() {
            // Negate the integer twice to turn it into a Boolean.
            return !(ReadVarUInt64() == 0);
        }

        /// <summary>
        /// Parses an unsigned LEB128 variable-length integer, limited to 7 bits.
        /// </summary>
        /// <returns>The parsed unsigned 7-bit integer.</returns>
        public byte ReadVarUInt7() {
            return (byte)ReadVarUInt64();
        }

        /// <summary>
        /// Parses an unsigned LEB128 variable-length integer, limited to 32 bits.
        /// </summary>
        /// <returns>The parsed unsigned 32-bit integer.</returns>
        public uint ReadVarUInt32() {
            return (uint)ReadVarUInt64();
        }

        /// <summary>
        /// Parses a signed LEB128 variable-length integer, limited to 64 bits.
        /// </summary>
        /// <returns>The parsed signed 64-bit integer.</returns>
        public long ReadVarInt64() {
            // C# translation of code borrowed from Wikipedia article:
            // https://en.wikipedia.org/wiki/LEB128

            long result = 0;
            int shift = 0;
            byte b;
            do {
                b = ReadByte();
                result |= ((long)(b & 0x7F) << shift);
                shift += 7;
            } while ((b & 0x80) != 0);

            // Sign bit of byte is second high order bit. (0x40)
            if ((shift < 64) && ((b & 0x40) == 0x40)) {
                // Sign extend.
                result |= -(1L << shift);
            }

            return result;
        }

        /// <summary>
        /// Parses a signed LEB128 variable-length integer, limited to 7 bits.
        /// </summary>
        /// <returns>The parsed signed 7-bit integer.</returns>
        public sbyte ReadVarInt7() {
            return (sbyte)ReadVarInt64();
        }

        /// <summary>
        /// Parses a signed LEB128 variable-length integer, limited to 32 bits.
        /// </summary>
        /// <returns>The parsed signed 32-bit integer.</returns>
        public int ReadVarInt32() {
            return (int)ReadVarInt64();
        }

        /// <summary>
        /// Parses a 32-bit floating-point number.
        /// </summary>
        /// <returns>The parsed 32-bit floating-point number.</returns>
        public float ReadFloat32() {
            var result = reader.ReadSingle();
            Position += sizeof(float);
            return result;
        }

        /// <summary>
        /// Parses a 64-bit floating-point number.
        /// </summary>
        /// <returns>The parsed 64-bit floating-point number.</returns>
        public double ReadFloat64() {
            var result = reader.ReadDouble();
            Position += sizeof(double);
            return result;
        }

        /// <summary>
        /// Reads a WebAssembly language type.
        /// </summary>
        /// <returns>The WebAssembly language type.</returns>
        public WasmType ReadWasmType() {
            return (WasmType)ReadVarInt7();
        }

        /// <summary>
        /// Reads a WebAssembly value type.
        /// </summary>
        /// <returns>The WebAssembly value type.</returns>
        public WasmValueType ReadWasmValueType() {
            return (WasmValueType)ReadVarInt7();
        }

        /// <summary>
        /// Parses a length-prefixed string.
        /// </summary>
        /// <returns>The parsed string.</returns>
        public string ReadString() {
            uint length = ReadVarUInt32();
            byte[] bytes = ReadBytes((int)length);
            return StringEncoding.GetString(bytes);
        }

        /// <summary>
        /// Reads resizable limits.
        /// </summary>
        /// <returns>The resizable limits.</returns>
        public ResizableLimits ReadResizableLimits() {
            bool hasMaximum = ReadVarUInt1();
            uint initial = ReadVarUInt32();
			uint? max = hasMaximum
                ? new uint?( ReadVarUInt32())
                : default( uint? );
            return new ResizableLimits(initial, max);
        }

        /// <summary>
        /// Parses a version header.
        /// </summary>
        /// <returns>The parsed version header.</returns>
        public VersionHeader ReadVersionHeader() {
            var result = new VersionHeader(reader.ReadUInt32(), reader.ReadUInt32());
            Position += 2 * sizeof(uint);
            return result;
        }

        /// <summary>
        /// Parses a section header.
        /// </summary>
        /// <returns>The parsed section header.</returns>
        public SectionHeader ReadSectionHeader() {
            var code = (SectionCode)ReadVarUInt7();
            uint payloadLength = ReadVarUInt32();
            if (code == SectionCode.Custom) {
                uint startPos = (uint)Position;
                var name = ReadString();
                uint nameLength = (uint)Position - startPos;
                return new SectionHeader(new SectionName(name), payloadLength - nameLength);
            }
            else {
                return new SectionHeader(new SectionName(code), payloadLength);
            }
        }

        /// <summary>
        /// Reads a section.
        /// </summary>
        /// <returns>The section.</returns>
        public Section ReadSection() {
            var header = ReadSectionHeader();
            return ReadSectionPayload(header);
        }

        /// <summary>
        /// Reads the section with the given header.
        /// </summary>
        /// <param name="header">The section header.</param>
        /// <returns>The parsed section.</returns>
        public Section ReadSectionPayload(SectionHeader header) {
            if (header.Name.IsCustom)
                return ReadCustomSectionPayload(header);
            else
                return ReadKnownSectionPayload(header);
        }

        /// <summary>
        /// Reads the remaining payload of the section whose payload starts at the given position.
        /// </summary>
        /// <param name="startPosition">The start of the section's payload.</param>
        /// <param name="payloadLength">The length of the section's payload, in bytes.</param>
        /// <returns>The remaining payload of the section whose payload starts at the given position.</returns>
        public byte[] ReadRemainingPayload(long startPosition, uint payloadLength) {
            return ReadBytes((int)(Position - startPosition - payloadLength));
        }

        /// <summary>
        /// Reads the remaining payload of the section whose payload starts at the given position.
        /// </summary>
        /// <param name="startPosition">The start of the section's payload.</param>
        /// <param name="header">The section's header.</param>
        /// <returns>The remaining payload of the section whose payload starts at the given position.</returns>
        public byte[] ReadRemainingPayload(long startPosition, SectionHeader header) {
            return ReadRemainingPayload(startPosition, header.PayloadLength);
        }

        /// <summary>
        /// Reads the custom section with the given header.
        /// </summary>
        /// <param name="header">The section header.</param>
        /// <returns>The parsed section.</returns>
        protected virtual Section ReadCustomSectionPayload(SectionHeader header) {
            if (header.Name.CustomName == NameSection.CustomName) {
                return NameSection.ReadSectionPayload(header, this);
            }
            else {
                return new CustomSection(
                    header.Name.CustomName,
                    ReadBytes((int)header.PayloadLength));
            }
        }

        /// <summary>
        /// Reads the non-custom section with the given header.
        /// </summary>
        /// <param name="header">The section header.</param>
        /// <returns>The parsed section.</returns>
        protected Section ReadKnownSectionPayload(SectionHeader header) {
            switch (header.Name.Code) {
                case SectionCode.Type:
                    return TypeSection.ReadSectionPayload(header, this);
                case SectionCode.Import:
                    return ImportSection.ReadSectionPayload(header, this);
                case SectionCode.Function:
                    return FunctionSection.ReadSectionPayload(header, this);
                case SectionCode.Table:
                    return TableSection.ReadSectionPayload(header, this);
                case SectionCode.Memory:
                    return MemorySection.ReadSectionPayload(header, this);
                case SectionCode.Global:
                    return GlobalSection.ReadSectionPayload(header, this);
                case SectionCode.Export:
                    return ExportSection.ReadSectionPayload(header, this);
                case SectionCode.Start:
                    return StartSection.ReadSectionPayload(header, this);
                case SectionCode.Element:
                    return ElementSection.ReadSectionPayload(header, this);
                case SectionCode.Code:
                    return CodeSection.ReadSectionPayload(header, this);
                case SectionCode.Data:
                    return DataSection.ReadSectionPayload(header, this);
                default:
                    return ReadUnknownSectionPayload(header);
            }
        }

        /// <summary>
        /// Reads the unknown, non-custom section with the given header.
        /// </summary>
        /// <param name="header">The section header.</param>
        /// <returns>The parsed section.</returns>
        protected virtual Section ReadUnknownSectionPayload(SectionHeader header) {
            return new UnknownSection(
                header.Name.Code,
                ReadBytes((int)header.PayloadLength));
        }

        /// <summary>
        /// Reads an entire WebAssembly file.
        /// </summary>
        /// <returns>The WebAssembly file.</returns>
        public WasmFile ReadFile() {
            var version = ReadVersionHeader();
            version.Verify();
            var sections = new List<Section>();
            while (!streamIsEmpty()) {
                sections.Add(ReadSection());
            }
            return new WasmFile(version, sections);
        }
    }
}