# encoding: ascii
# api: powershell
# title: lost operators
# description: Adds imitations of some operators such as ternary operator, -all, -any and etc.
# version: 0.1
# type: script
# author: poshman
# license: CC0
# function: Invoke-LostOperator
# x-poshcode-id: 6492
# x-archived: 2016-09-04T23:41:46
# x-published: 2016-08-29T07:34:00
#
#
# Script is not mine, see
# Post on Twitter https://mobile.twitter.com/gregzakharov/status/769974270046904320


Set-Alias ~! Invoke-LostOperator

function Invoke-LostOperator {
  <#
    .SYNOPSIS
        Compensates lack of some operators.
    .DESCRIPTION
        This function has alias "~!". It seems the best way to warn user about that an
        operator does not really exist.
    .EXAMPLE
        PS C:\> ~! @(1, 2, 3, 4, 5) -any '($x % 2) -eq 0'
        True
    .EXAMPLE
        PS C:\> ~! @(2, 4, 6, 8, 10) -all '($x % 2) -eq 0'
        False
    .EXAMPLE
        PS C:\> ~! @('this', 'is', 'array', 1, 2, 3) -first '$x.GetTtpe().Name -eq "Int32"'
        1
    .EXAMPLE
        PS C:\> ~! @('this', 'is', 'array', 1, 2, 3) -last '$x.GetType().Name -eq "String"'
        array
    .EXAMPLE
        PS C:\> ~! 7 -shl 3
        56
    .EXAMPLE
        PS C:\> ~! 7 -shr 1
        3
    .EXAMPLE
        PS C:\> ~! @('this', 'is', 'array', 1, 2, 3) -skip 3
        1
        2
        3
    .EXAMPLE
        PS C:\> ~! @('this', 'is', 'array', 1, 2, 3) -take 3
        this
        is
        array
    .EXAMPLE
        PS C:\> $x = 10 # do something with this variable
        PS C:\> ~! ($x -gt 30) ? { $x + 5 } : { $x - 3 }
    .NOTES
        Operators -shl and -shr are not required for usage on PowerShell -ge 3.
        
        Alternative way to simulate a shift method on PowerShell -eq 2:
        PS C:\> $(cmd /c set /a 7 ^<^< 3);''
        56
        PS C:\> $(cmd /c set /a 7 ^>^> 1);''
        3
        
        To get all PowerShell -eq 2 operators:
        if ($PSVersionTable.PSVersion.Major -eq 2) {
          [PSObject].Assembly.GetType(
            'System.Management.Automation.OperatorTokenReader'
          ).GetFields([Reflection.BindingFlags]40) |
          Where-Object { $_.Name -match 'operators\Z' } |
          Sort-Object Name | ForEach-Object {
            Write-Host "$($_.Name.Replace(
              'Operators', ''
            )): " -ForegroundColor Green -NoNewLine
            Write-Host (
              $_.GetValue($null) -join ', '
            ) -ForegroundColor Yellow
          }
        }
  #>
  begin {
    if ($PSVersionTable.PSVersion.Major -eq 2) {
      @(
        [Reflection.Emit.DynamicMethod],
        [Reflection.Emit.OpCodes]
      ) | ForEach-Object {
        $keys = ($ta = [PSObject].Assembly.GetType(
          'System.Management.Automation.TypeAccelerators'
        ))::Get.Keys
        $collect = @()
      }{
        if ($keys -notcontains $_.Name) {
          $ta::Add($_.Name, $_)
        }
        $collect += $_.Name
      }
      
      function private:Set-Shift {
        param(
          [Parameter(Position=0)]
          [ValidateNotNullOrEmpty()]
          [ValidateSet('Left', 'Right')]
          [String]$Direction = 'Left',
          
          [Parameter(Position=1)]
          [ValidateNotNull()]
          [Object]$Type = [Int32]
        )
        
        @(
          'Ldarg_0'
          'Ldarg_1'
          'Ldc_I4_S, 31'
          'And'
          $(if ($Direction -eq 'Right') { 'Shr' } else { 'Shl' })
          'Ret'
        ) | ForEach-Object {
          $def = New-Object DynamicMethod(
            $Direction, $Type, @($Type, $Type)
          )
          $il = $def.GetILGenerator()
        }{
          if ($_ -notmatch ',') { $il.Emit([OpCodes]::$_) }
          else {
            $il.Emit(
              [OpCodes]::(($$ = $_.Split(','))[0]), ($$[1].Trim() -as $Type)
          )}
        }
        
        $def.CreateDelegate((
          Invoke-Expression "[Func[$($Type.Name), $($Type.Name), $($Type.Name)]]"
        ))
      }
    }
    
    function private:Invoke-Eval {
      param(
        [Parameter(Mandatory=$true)]
        [Object]$Object
      )
      
      if ($Object -ne $null) {
        if ($Object -is 'ScriptBlock') {
          return &$Object
        }
        return $Object
      }
      
      return $null
    }
    
    function private:Invoke-Linq {
      param(
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [String]$Method,
        
        [Parameter(Mandatory=$true, Position=1)]
        [ValidateNotNull()]
        [Object[]]$Collection,
        
        [Parameter(Mandatory=$true, Position=2)]
        [ValidateNotNullOrEmpty()]
        [String]$Condition,
        
        [Parameter(Position=3)]
        [Type[]]$Type = [Object]
      )
      
      $m = ([Linq.Enumerable].GetMember(
        $Method, [Reflection.MemberTypes]8,
        [Reflection.BindingFlags]24
      ) | Where-Object {
        $_.IsGenericMethod -and $_.GetParameters(
        ).Length -eq 2
      }).MakeGenericMethod($Type)
      
      switch -Regex ($Method) {
        '\A(Skip|Take)\Z' {
          $i = 0
          if (![Int32]::TryParse($Condition, [ref]$i)) {
            return $null
          }
          
          $m.Invoke($null, @($Collection, $i))
        }
        '\A(All|Any|First|Last)\Z' {
          $e = Invoke-Expression "[Func[$Type, Boolean]]{param(
            $(([Regex]'\$\w+').Match($Condition).Value)
          ) $Condition}"
          
          $m.Invoke($null, @($Collection, $e))
        }
      }
    }
  }
  process {
    if ($args) {
      $l = [Array]::IndexOf($args, $args[1]) + 1
      switch ($args.Length) {
        3 {
          switch -Regex ($args[1]) {
            '\A-(any|all|first|last|skip|take)\Z' {
              return Invoke-Linq "$([Char]::ToUpper($args[1][1])
              )$(-join $args[1][2..$args[1].Length])" $args[0] $args[$l]
            }
            '\A-sh(l|r)\Z' { # do not use it on PowerShell -ge 3
              if ($collect) {
                return (Set-Shift $(switch (
                  $args[1][-1]) {'l'{'Left'}'r'{'Right'}})
                ).Invoke($args[0], $args[$l])
              }
            }
          }
        } # -any, -all, -first, -last, -shl, -shr, -skip and -take
        5 {
          if (Invoke-Eval $args[0]) {
            return Invoke-Eval $args[$l]
          }
          return Invoke-Eval ($args[[Array]::IndexOf($args, ':', $l) + 1])
        } # ternary
      }
      
      return Invoke-Eval $args[0]
    }
  }
  end {
    if ($collect) { $collect | ForEach-Object { [void]$ta::Remove($_) } }
  }
}
