Wasm/ImportSection.cs
using System.Collections.Generic;
using System.IO;
using WasmBox.Wasm.Binary;

namespace WasmBox.Wasm {
    /// <summary>
    /// A type of section that imports values.
    /// </summary>
    public sealed class ImportSection : Section {
        /// <summary>
        /// Creates an empty import section.
        /// </summary>
        public ImportSection() {
            this.Imports = new List<ImportedValue>();
            this.ExtraPayload = new byte[0];
        }

        /// <summary>
        /// Creates an import section from a sequence of imports.
        /// </summary>
        /// <param name="imports">A sequence of imports to put in the import section.</param>
        public ImportSection(IEnumerable<ImportedValue> imports)
            : this(imports, new byte[0]) {
        }

        /// <summary>
        /// Creates an import section from a sequence of imports and a trailing payload.
        /// </summary>
        /// <param name="imports">A sequence of imports to put in the import section.</param>
        /// <param name="extraPayload">
        /// A sequence of bytes that have no intrinsic meaning; they are part
        /// of the import section but are placed after the import section's actual contents.
        /// </param>
        public ImportSection(IEnumerable<ImportedValue> imports, byte[] extraPayload) {
            this.Imports = new List<ImportedValue>(imports);
            this.ExtraPayload = extraPayload;
        }

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

        /// <summary>
        /// Gets the list of all values that are exported by this section.
        /// </summary>
        /// <returns>A list of all values exported by this section.</returns>
        public List<ImportedValue> Imports { get; private set; }

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

        /// <inheritdoc/>
        public override void WritePayloadTo(BinaryWasmWriter writer) {
            writer.WriteVarUInt32((uint)Imports.Count);
            foreach (var import in Imports) {
                import.WriteTo(writer);
            }
            writer.Writer.Write(ExtraPayload);
        }

        /// <summary>
        /// Reads the import section with the given header.
        /// </summary>
        /// <param name="header">The section header.</param>
        /// <param name="reader">A reader for a binary WebAssembly file.</param>
        /// <returns>The parsed section.</returns>
        public static ImportSection ReadSectionPayload(
            SectionHeader header, BinaryWasmReader reader) {
            long startPos = reader.Position;
            // Read the imported values.
            uint count = reader.ReadVarUInt32();
            var importedVals = new List<ImportedValue>();
            for (uint i = 0; i < count; i++) {
                importedVals.Add(ImportedValue.ReadFrom(reader));
            }

            // Skip any remaining bytes.
            var extraPayload = reader.ReadRemainingPayload(startPos, header);
            return new ImportSection(importedVals, extraPayload);
        }

        /// <inheritdoc/>
        public override void Dump(TextWriter writer) {
            writer.Write(Name.ToString());
            writer.Write("; number of entries: ");
            writer.Write(Imports.Count);
            writer.WriteLine();
            for (int i = 0; i < Imports.Count; i++) {
                writer.Write("#");
                writer.Write(i);
                writer.Write(" -> ");
                Imports[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>
    /// An entry in an import section.
    /// </summary>
    public abstract class ImportedValue {
        /// <summary>
        /// Creates an import value from the given pair of names.
        /// </summary>
        /// <param name="moduleName">The name of the module from which a value is imported.</param>
        /// <param name="fieldName">The name of the value that is imported.</param>
        public ImportedValue(string moduleName, string fieldName) {
            this.ModuleName = moduleName;
            this.FieldName = fieldName;
        }

        /// <summary>
        /// Gets or sets the name of the module from which a value is imported.
        /// </summary>
        /// <returns>The name of the module from which a value is imported.</returns>
        public string ModuleName { get; set; }

        /// <summary>
        /// Gets or sets the name of the value that is imported.
        /// </summary>
        /// <returns>The name of the value that is imported.</returns>
        public string FieldName { get; set; }

        /// <summary>
        /// Gets the kind of value that is exported.
        /// </summary>
        /// <returns>The kind of value that is exported.</returns>
        public abstract ExternalKind Kind { get; }

        /// <summary>
        /// Writes the contents of this imported value to the given binary WebAssembly writer.
        /// </summary>
        /// <param name="writer">A WebAssembly writer.</param>
        protected abstract void WriteContentsTo(BinaryWasmWriter writer);

        /// <summary>
        /// Dumps the contents of this imported value to the given text writer.
        /// </summary>
        /// <param name="writer">A text writer.</param>
        protected abstract void DumpContents(TextWriter writer);

        /// <summary>
        /// Writes this exported value to the given WebAssembly file writer.
        /// </summary>
        /// <param name="writer">The WebAssembly file writer.</param>
        public void WriteTo(BinaryWasmWriter writer) {
            writer.WriteString(ModuleName);
            writer.WriteString(FieldName);
            writer.Writer.Write((byte)Kind);
            WriteContentsTo(writer);
        }

        /// <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(
                "from \"{0}\" import {1} \"{2}\": ",
                ModuleName,
                ((object)Kind).ToString().ToLower(),
                FieldName);
            DumpContents(writer);
        }

        /// <summary>
        /// Reads an imported value from the given binary WebAssembly reader.
        /// </summary>
        /// <param name="reader">The WebAssembly reader.</param>
        /// <returns>The imported value that was read.</returns>
        public static ImportedValue ReadFrom(BinaryWasmReader reader) {
            string moduleName = reader.ReadString();
            string fieldName = reader.ReadString();
            var kind = (ExternalKind)reader.ReadByte();
            switch (kind) {
                case ExternalKind.Function:
                    return new ImportedFunction(moduleName, fieldName, reader.ReadVarUInt32());
                case ExternalKind.Global:
                    return new ImportedGlobal(moduleName, fieldName, GlobalType.ReadFrom(reader));
                case ExternalKind.Memory:
                    return new ImportedMemory(moduleName, fieldName, MemoryType.ReadFrom(reader));
                case ExternalKind.Table:
                    return new ImportedTable(moduleName, fieldName, TableType.ReadFrom(reader));
                default:
                    throw new WasmException("Unknown imported value kind: " + kind);
            }
        }
    }

    /// <summary>
    /// Describes an entry in the import section that imports a function.
    /// </summary>
    public sealed class ImportedFunction : ImportedValue {
        /// <summary>
        /// Creates a function import from the given module name, field and function index.
        /// </summary>
        /// <param name="moduleName">The name of the module from which a value is imported.</param>
        /// <param name="fieldName">The name of the value that is imported.</param>
        /// <param name="typeIndex">The type index of the function signature.</param>
        public ImportedFunction(string moduleName, string fieldName, uint typeIndex)
            : base(moduleName, fieldName) {
            this.TypeIndex = typeIndex;
        }

        /// <summary>
        /// Gets or sets the type index of the function signature.
        /// </summary>
        /// <returns>The type index of the function signature.</returns>
        public uint TypeIndex { get; set; }

        /// <inheritdoc/>
        public override ExternalKind Kind => ExternalKind.Function;

        /// <inheritdoc/>
        protected override void DumpContents(TextWriter writer) {
            writer.Write("type #{0}", TypeIndex);
        }

        /// <inheritdoc/>
        protected override void WriteContentsTo(BinaryWasmWriter writer) {
            writer.WriteVarUInt32(TypeIndex);
        }
    }

    /// <summary>
    /// Describes an entry in the import section that imports a table.
    /// </summary>
    public sealed class ImportedTable : ImportedValue {
        /// <summary>
        /// Creates a table import from the given module name, field and table type.
        /// </summary>
        /// <param name="moduleName">The name of the module from which a value is imported.</param>
        /// <param name="fieldName">The name of the value that is imported.</param>
        /// <param name="table">A description of the imported table.</param>
        public ImportedTable(string moduleName, string fieldName, TableType table)
            : base(moduleName, fieldName) {
            this.Table = table;
        }

        /// <summary>
        /// Gets or sets a description of the table that is imported.
        /// </summary>
        /// <returns>A description of the table that is imported.</returns>
        public TableType Table { get; set; }

        /// <inheritdoc/>
        public override ExternalKind Kind => ExternalKind.Table;

        /// <inheritdoc/>
        protected override void DumpContents(TextWriter writer) {
            Table.Dump(writer);
        }

        /// <inheritdoc/>
        protected override void WriteContentsTo(BinaryWasmWriter writer) {
            Table.WriteTo(writer);
        }
    }

    /// <summary>
    /// Describes an entry in the import section that imports a linear memory.
    /// </summary>
    public sealed class ImportedMemory : ImportedValue {
        /// <summary>
        /// Creates a memory import from the given module name, field and memory type.
        /// </summary>
        /// <param name="moduleName">The name of the module from which a value is imported.</param>
        /// <param name="fieldName">The name of the value that is imported.</param>
        /// <param name="memory">A description of the imported memory.</param>
        public ImportedMemory(string moduleName, string fieldName, MemoryType memory)
            : base(moduleName, fieldName) {
            this.Memory = memory;
        }

        /// <summary>
        /// Gets or sets a description of the table that is imported.
        /// </summary>
        /// <returns>A description of the table that is imported.</returns>
        public MemoryType Memory { get; set; }

        /// <inheritdoc/>
        public override ExternalKind Kind => ExternalKind.Memory;

        /// <inheritdoc/>
        protected override void DumpContents(TextWriter writer) {
            Memory.Dump(writer);
        }

        /// <inheritdoc/>
        protected override void WriteContentsTo(BinaryWasmWriter writer) {
            Memory.WriteTo(writer);
        }
    }

    /// <summary>
    /// Describes an entry in the import section that imports a global variable.
    /// </summary>
    public sealed class ImportedGlobal : ImportedValue {
        /// <summary>
        /// Creates a global import from the given module name, field and global type.
        /// </summary>
        /// <param name="moduleName">The name of the module from which a value is imported.</param>
        /// <param name="fieldName">The name of the value that is imported.</param>
        /// <param name="global">A description of the imported global.</param>
        public ImportedGlobal(string moduleName, string fieldName, GlobalType global)
            : base(moduleName, fieldName) {
            this.Global = global;
        }

        /// <summary>
        /// Gets or sets a description of the global variable that is imported.
        /// </summary>
        /// <returns>A description of the global variable that is imported.</returns>
        public GlobalType Global { get; set; }

        /// <inheritdoc/>
        public override ExternalKind Kind => ExternalKind.Global;

        /// <inheritdoc/>
        protected override void DumpContents(TextWriter writer) {
            Global.Dump(writer);
        }

        /// <inheritdoc/>
        protected override void WriteContentsTo(BinaryWasmWriter writer) {
            Global.WriteTo(writer);
        }
    }
}