Legislator.psm1

using namespace System.Collections.Generic
using namespace System.Reflection
using namespace System.Reflection.Emit

function event {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$TypeName,

        [Parameter(Mandatory = $true, Position = 1)]
        [Alias('Name')]
        [string]$EventName,

        [Parameter(Mandatory = $false, Position = 2)]
        [string]$Option
    )

    Assert-Legislator -MemberType event

    try{
        $handlerType = [Type]$TypeName
    }
    catch {
        throw [Exception]::new('Unrecognized type name', $_)
        return
    }

    $eventBuilder = $Legislator.DefineEvent($EventName, [EventAttributes]::None, $handlerType);

    $eventMethodAttributes = @(
        'Public', 'HideBySig', 'SpecialName', 'Abstract', 'Virtual', 'NewSlot'
    ) -as [MethodAttributes]

    $addMethod = . method -TypeName:'void' -Name:"add_$EventName" -Attributes:$eventMethodAttributes -ParameterTypes @( $HandlerType ) -PassThru:$true
    $addMethod.DefineParameter(1, [ParameterAttributes]::None, 'value')
    $eventBuilder.SetAddOnMethod($addMethod)

    $removeMethod = . method -TypeName:'void' -Name:"remove_$EventName" -Attributes:$eventMethodAttributes -ParameterTypes @( $HandlerType ) -PassThru:$true
    $removeMethod.DefineParameter(1, [ParameterAttributes]::None, 'value')
    $eventBuilder.SetRemoveOnMethod($removeMethod)
}

function interface {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidatePattern('^[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}][\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}]*$')]
        [string]$Name,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateScript({
            -not (
                @($_.Ast.FindAll({
                    param($AST)
                    $AST -is [System.Management.Automation.Language.CommandAst]
                },$true) |ForEach-Object GetCommandName) |Where-Object { $_ -notin 'property','method','event' }
            ) -or $(throw 'Only properties and methods can be defined in an interface')
        })]
        [scriptblock]$Definition,

        [Parameter()]
        [ValidateScript({-not($_ |Where-Object{-not $_.IsInterface})})]
        [type[]]$Implements,

        [Parameter()]
        [switch]$PassThru = $false
    )

    if($Name -cnotlike 'I*'){
        Write-Warning -Message "Naming rule violaion: Missing prefix: 'I'"
    }

    $interfaceAttributes = @(
        'Public','Interface','Abstract','AnsiClass','AutoLayout'
    ) -as [TypeAttributes]

    $runtimeGuid = $((New-Guid)-replace'\W')

    $assemblyName = [AssemblyName]::new("$Name$runtimeGuid")
    $assemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($assemblyName, [AssemblyBuilderAccess]::Run)
    $moduleBuilder = $assemblyBuilder.DefineDynamicModule("__psinterfacemodule_$assemblyName")

    $Legislator = $moduleBuilder.DefineType($Name, $interfaceAttributes)

    if($PSBoundParameters.ContainsKey('Implements')){
        foreach($interfaceImpl in $Implements |Sort-Object -Property FullName -Unique){
            try{
                $Legislator.AddInterfaceImplementation($interfaceImpl)
            }
            catch{
                throw
                return
            }
        }
    }

    $null = . $Definition

    if($?){
        $finalType = $Legislator.CreateType()

        if($PassThru){
            return $finalType
        }
    }
}

function Assert-Legislator
{
    param (
        [string]$MemberType
    )

    if (-not $Legislator)
    {
        throw "$MemberType only allowed in interface declarations"
    }
}

function method {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$TypeName,

        [Parameter(Mandatory = $true, Position = 1)]
        [Alias('Name')]
        [string]$MethodName,

        [Parameter(Mandatory = $false, Position = 2)]
        [AllowEmptyCollection()]
        [Type[]]$ParameterTypes,

        [Parameter(DontShow)]
        [MethodAttributes]$Attributes,

        [Parameter(DontShow)]
        [switch]$PassThru
    )

    Assert-Legislator -MemberType method

    try{
        $ReturnType = [Type]$TypeName
    }
    catch {
        throw [Exception]::new('Unrecognized type name', $_)
        return
    }

    $interfaceMethodAttributes = @(
        'Public', 'HideBySig', 'Abstract', 'Virtual', 'NewSlot'
    ) -as [MethodAttributes]

    $interfaceMethodAttributes = $interfaceMethodAttributes -bor $Attributes

    $method = $Legislator.DefineMethod($MethodName, $interfaceMethodAttributes, $ReturnType, $ParameterTypes)
    if($PassThru){
        return $method
    }
}

function property {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$TypeName,

        [Parameter(Mandatory = $true, Position = 1)]
        [Alias('Name')]
        [string]$PropertyName,

        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateSet('ReadOnly')]
        [string]$Option
    )

    Assert-Legislator -MemberType property

    try{
        $Type = [Type]$TypeName
    }
    catch {
        throw [Exception]::new('Unrecognized type name', $_)
        return
    }

    $property = $Legislator.DefineProperty($PropertyName, [PropertyAttributes]::HasDefault, [CallingConventions]::HasThis, $Type, $null)

    $propertyMethodAttributes = @(
        'Public', 'HideBySig', 'SpecialName', 'Abstract', 'Virtual', 'NewSlot'
    ) -as [MethodAttributes]

    $getMethod = . method -TypeName:$Type.FullName -Name:"get_$PropertyName" -Attributes:$propertyMethodAttributes -PassThru:$true
    $property.SetGetMethod($getMethod)

    if($Option -ne 'ReadOnly') {
        $setMethod = . method -TypeName:"void" -Name:"set_$PropertyName" -Attributes:$propertyMethodAttributes -ParameterTypes @( $Type ) -PassThru:$true
        $null = $setMethod.DefineParameter(1, [ParameterAttributes]::None, 'value');

        $property.SetSetMethod($setMethod)
    }
}