Source: types/ArgumentDef.js

import { MemoryManager } from '../MemoryManager'

import { ValueType } from './ValueType'
import { Type } from './Type'

/**
 * An argument definition is used by {@link FunctionPrototype} to define the
 * arguments to a function.
 * @template T
 */
export class ArgumentDef {
  /**
   * Construct an argument definition. The {@link In}, {@link Out}, and {@link InOut}
   * helper classes should be used to construct argument definitions as they are
   * semantically clearer and avoid the possibility of setting both `isInput` and
   * `isOutput` to `false`.
   * @param {Type<T>} type The argument type
   * @param {boolean} isInput If true the argument provides data to the function
   * @param {boolean} isOutput If true the argument is populated by the function
   */
  constructor (type, isInput, isOutput) {
    this.type = type
    this.isInput = isInput
    this.isOutput = isOutput
  }

  /**
   * Create a representation of the JavaScript value which can be passed to a
   * WebAssembly module instance. For value types this is typically the value
   * itself. For reference types memory will be allocated in the instance, and
   * the data will be copied.
   * @param {MemoryManager} memoryManager A class which provides methods to
   * @param {number} unmarshalledIndex The index of the unmarshalled value
   * @param {Array<*>} unmarshalledArgs The unmarshalled arguments
   * @returns {number|T} The address of the allocated memory or the marshalled value.
   * @throws {Error} If the argument is not input and/or output.
   */
  marshall (memoryManager, unmarshalledIndex, unmarshalledArgs) {
    if (this.type instanceof ValueType) {
      return unmarshalledArgs[unmarshalledIndex]
    } else if (this.isInput) {
      return this.type.marshall(memoryManager, unmarshalledIndex, unmarshalledArgs)
    } else if (this.isOutput) {
      return this.type.alloc(memoryManager, unmarshalledIndex, unmarshalledArgs)
    } else {
      throw new Error('An argument must be input and/or output')
    }
  }

  /**
   * Unmarshall a value
   * @param {MemoryManager} memoryManager The memory manager
   * @param {number|T} addressOrValue The marshalled address or value 
   * @param {number} unmarshalledIndex The index of the unmarshalled value or -1
   * @param {Array<*>} unmarshalledArgs The unmarshall args
   * @returns {number|T} The unmarshalled value.
   */
  unmarshall (memoryManager, addressOrValue, unmarshalledIndex, unmarshalledArgs) {
    if (this.type instanceof ValueType) {
      return addressOrValue
    }

    if (this.isOutput) {
      if (unmarshalledIndex === -1) {
        throw new Error('Out put argument missing')
      }
      if (typeof addressOrValue !== 'number') {
        throw new Error('Expected address or to be a number')
      }
      const result = this.type.unmarshall(memoryManager, addressOrValue, unmarshalledIndex, unmarshalledArgs)
      this.type.copy(unmarshalledArgs[unmarshalledIndex], result)
    } else {
      if (typeof addressOrValue !== 'number') {
        throw new Error('Expected address to be a number')
      }
      this.type.free(memoryManager, addressOrValue, unmarshalledIndex, unmarshalledArgs)
    }
  }
}