Code/Wasm/TypeSection.cs
using System.Collections.Generic;
using System.IO;
using System.Linq;
using WasmBox.Wasm.Binary;

namespace WasmBox.Wasm {
    /// <summary>
    /// Represents a type section in a WebAssembly file.
    /// </summary>
    public sealed class TypeSection : Section {
        /// <summary>
        /// Creates an empty type section.
        /// </summary>
        public TypeSection()
            : this(Enumerable.Empty<FunctionType>()) {
        }

        /// <summary>
        /// Creates a type section from the given list of function types.
        /// </summary>
        /// <param name="functionTypes">The list of function types in this type section.</param>
        public TypeSection(IEnumerable<FunctionType> functionTypes)
            : this(functionTypes, new byte[0]) {
        }

        /// <summary>
        /// Creates a type section from the given list of function types and an additional payload.
        /// </summary>
        /// <param name="functionTypes">The list of function types in this type section.</param>
        /// <param name="extraPayload">The additional payload for this section, as an array of bytes.</param>
        public TypeSection(IEnumerable<FunctionType> functionTypes, byte[] extraPayload) {
            this.FunctionTypes = new List<FunctionType>(functionTypes);
            this.ExtraPayload = extraPayload;
        }

        /// <summary>
        /// Gets this type section's list of function types.
        /// </summary>
        /// <returns>The list of function types in this type section.</returns>
        public List<FunctionType> FunctionTypes { get; private set; }

        /// <summary>
        /// This type section's additional payload.
        /// </summary>
        /// <returns>The additional payload, as an array of bytes.</returns>
        public byte[] ExtraPayload { get; set; }

        /// <inheritdoc/>
        public override SectionName Name => new SectionName(SectionCode.Type);

        /// <inheritdoc/>
        public override void WritePayloadTo(BinaryWasmWriter writer) {
            writer.WriteVarUInt32((uint)FunctionTypes.Count);
            foreach (var type in FunctionTypes)
                type.WriteTo(writer);

            writer.Writer.Write(ExtraPayload);
        }

        /// <inheritdoc/>
        public override void Dump(TextWriter writer) {
            writer.Write(Name.ToString());
            writer.Write("; number of entries: ");
            writer.Write(FunctionTypes.Count);
            writer.WriteLine();
            for (int i = 0; i < FunctionTypes.Count; i++) {
                writer.Write("#");
                writer.Write(i);
                writer.Write(" -> ");
                FunctionTypes[i].Dump(writer);
                writer.WriteLine();
            }
            if (ExtraPayload.Length > 0) {
                writer.Write("Extra payload size: ");
                writer.Write(ExtraPayload.Length);
                writer.WriteLine();
                DumpHelpers.DumpBytes(ExtraPayload, writer);
                writer.WriteLine();
            }
        }

        /// <summary>
        /// Reads a type section's payload from the given binary WebAssembly reader.
        /// </summary>
        /// <param name="header">The type section's header.</param>
        /// <param name="reader">A reader for a binary WebAssembly file.</param>
        /// <returns>A parsed type section.</returns>
        public static TypeSection ReadSectionPayload(SectionHeader header, BinaryWasmReader reader) {
            long initPos = reader.Position;
            uint typeCount = reader.ReadVarUInt32();
            var types = new List<FunctionType>((int)typeCount);
            for (uint i = 0; i < typeCount; i++) {
                types.Add(FunctionType.ReadFrom(reader));
            }
            var extraBytes = reader.ReadRemainingPayload(initPos, header);
            return new TypeSection(types, extraBytes);
        }
    }

    /// <summary>
    /// Represents a function type entry in a type section.
    /// </summary>
    public sealed class FunctionType {
        /// <summary>
        /// Creates a function type.
        /// </summary>
        public FunctionType() {
            this.ParameterTypes = new List<WasmValueType>();
            this.ReturnTypes = new List<WasmValueType>();
        }

        /// <summary>
        /// Creates a function type from the given parameter types and return types.
        /// </summary>
        /// <param name="parameterTypes">This function type's list of parameter types.</param>
        /// <param name="returnTypes">This function type's list of return types.</param>
        public FunctionType(
            IEnumerable<WasmValueType> parameterTypes,
            IEnumerable<WasmValueType> returnTypes) {
            this.ParameterTypes = new List<WasmValueType>(parameterTypes);
            this.ReturnTypes = new List<WasmValueType>(returnTypes);
        }

        /// <summary>
        /// Creates a function type that takes ownership of the given parameter types and return types.
        /// </summary>
        /// <param name="parameterTypes">This function type's list of parameter types.</param>
        /// <param name="returnTypes">This function type's list of return types.</param>
        private FunctionType(
            List<WasmValueType> parameterTypes,
            List<WasmValueType> returnTypes) {
            this.ParameterTypes = parameterTypes;
            this.ReturnTypes = returnTypes;
        }

        /// <summary>
        /// Gets this function type's form, which is always WasmType.Func.
        /// </summary>
        public WasmType Form => WasmType.Func;

        /// <summary>
        /// Gets this function type's list of parameter types.
        /// </summary>
        /// <returns>The list of parameter types for this function.</returns>
        public List<WasmValueType> ParameterTypes { get; private set; }

        /// <summary>
        /// Gets this function type's list of return types.
        /// </summary>
        /// <returns>The list of return types for this function.</returns>
        public List<WasmValueType> ReturnTypes { get; private set; }

        /// <summary>
        /// Writes this function type to the given binary WebAssembly file.
        /// </summary>
        /// <param name="writer">The writer for a binary WebAssembly file.</param>
        public void WriteTo(BinaryWasmWriter writer) {
            writer.WriteWasmType(Form);
            writer.WriteVarUInt32((uint)ParameterTypes.Count);
            foreach (var item in ParameterTypes)
                writer.WriteWasmValueType(item);

            writer.WriteVarUInt32((uint)ReturnTypes.Count);
            foreach (var item in ReturnTypes)
                writer.WriteWasmValueType(item);
        }

        /// <summary>
        /// Writes a textual representation of this exported value to the given writer.
        /// </summary>
        /// <param name="writer">The writer to which text is written.</param>
        public void Dump(TextWriter writer) {
            writer.Write("func(");
            for (int i = 0; i < ParameterTypes.Count; i++) {
                if (i > 0)
                    writer.Write(", ");

                DumpHelpers.DumpWasmType(ParameterTypes[i], writer);
            }
            writer.Write(") returns (");
            for (int i = 0; i < ReturnTypes.Count; i++) {
                if (i > 0)
                    writer.Write(", ");

                DumpHelpers.DumpWasmType(ReturnTypes[i], writer);
            }
            writer.Write(")");
        }

        /// <inheritdoc/>
        public override string ToString() {
            var writer = new StringWriter();
            Dump(writer);
            return writer.ToString();
        }

        /// <summary>
        /// Reads a single function type from the given reader.
        /// </summary>
        /// <returns>The function type.</returns>
        public static FunctionType ReadFrom(BinaryWasmReader reader) {
            WasmType form = (WasmType)reader.ReadWasmType();
            if (form != WasmType.Func)
                throw new WasmException("Invalid 'form' value ('" + form + "') for function type.");

            uint paramCount = reader.ReadVarUInt32();
            var paramTypes = new List<WasmValueType>((int)paramCount);
            for (uint i = 0; i < paramCount; i++) {
                paramTypes.Add(reader.ReadWasmValueType());
            }

            uint retCount = reader.ReadVarUInt32();
            var retTypes = new List<WasmValueType>((int)retCount);
            for (uint i = 0; i < retCount; i++) {
                retTypes.Add(reader.ReadWasmValueType());
            }

            return new FunctionType(paramTypes, retTypes);
        }
    }
}