Transpilers/Core/PipeScript.Translator.psx.ps1

<#
.SYNOPSIS
    PipeScript Translator
.DESCRIPTION
    Allows optional translation of PowerShell into another language.

    For this to work, the language must support this translation (by implementing `TranslatePowerShell()` or `TranslateNameOfASTType`).

    Any pair of statements taking the form `... in <Language>` will attempt a translation.

.EXAMPLE
    {
        enum Foo {
            Bar = 1
            Baz = 2
            Bing = 3
        } in CSharp
    } | Use-PipeScript
#>

[ValidateScript({
    $validating = $_
    # This only applies to commands:
    if ($validating -isnot [Management.Automation.Language.CommandAst]) {
        return $false
    }

    # * That start with '-?in/as'
    if ($validating.CommandElements[0].Value -notmatch '^-?[in|as]') {
        return $false
    }

    # * That contain exactly two elements,
    if ($validating.CommandElements.Count -ne 2) { return $false }

    # * Whose second element is a bareword,
    if ($validating.CommandElements[1] -isnot [Management.Automation.Language.StringConstantExpressionAst]) { return $false }

    # * That are within a pipeline with multiple statements
    if (-not $validating.Parent.Parent.Statements) { return $false }    
    
    # * That are not the first statement.
    $statementIndex = $validating.Parent.Parent.Statements.IndexOf($validating.Parent)
    if ($statementIndex -le 0) {
        return $false
    }
    return $true
})]
param(
# The CommandAST
[Parameter(Mandatory,ValueFromPipeline,ParameterSetName='CommandAst')]
[Management.Automation.Language.CommandAst]
$CommandAst
)

process {
    # If this command was not within statements, return.
    if (-not $CommandAst.Parent.Parent.Statements) { return }
    # Determine the index of this statement
    $statementIndex = $CommandAst.Parent.Parent.Statements.IndexOf($CommandAst.Parent)
    # and get the previous statement.
    $previousStatement = $CommandAst.Parent.Parent.Statements[$statementIndex -1]

    # If we don't have a previous statement, return.
    if (-not $previousStatement) { return }

    # Determine the desired language. It should be the second element's bareword
    $desiredLanguage = $CommandAst.CommandElements[1].Value

    # Get all of the language commands
    $languageCommands = Get-PipeScript -PipeScriptType Language
    # and pick out this one
    $languageIsDefined = $languageCommands -match "^Language\.$desiredLanguage"

    # If we don't have a match, return.
    if (-not $languageIsDefined) { return }
    
    # Determine the type of the previous statement
    $previousStatementType = $previousStatement.GetType()

    # Translation methods can have a few different prefixes to their names
    # * TranslateFrom
    # * ConvertFrom
    # * Convert
    # * Translate
    # * From
    # * Visit

    $translationPrefix = "(?>$("TranslateFrom", "ConvertFrom", "Convert", "Translate", "From", "Visit" -join '|'))"

    # The typename will make up the next portion of the method name.
    # It can be one of three things:
    # * The fullname of the type
    # * The name of the type
    # * The name of the type, minus the suffixes 'Ast' or 'Syntax'
    $translationSuffix = "(?>$(
        @(
            $previousStatementType.FullName
            # Or followed by the name
            $previousStatementType.Name -replace '(?>Ast|Syntax)$'

            $previousStatementType.Name -replace '\.'
        ) -join '|'
    ))"


    
    # And attempt to translate to each potential matching language
    $translationResult = foreach ($definedLanguageCommand in $languageIsDefined) {
        # Running the language command will give us it's definition.
        $definedLanguage = & $definedLanguageCommand
        # Now we look over each potential method for a translator
        switch -regex ($definedLanguage.PSObject.Members.Name) {
            # It's name could be the prefix and the suffix
            "^${translationPrefix}${translationSuffix}" {
                
                $translationMethod = $definedLanguage.$_
                # If we found a matching translation method, run it
                $translationOutput = $translationMethod.Invoke($previousStatement)
                if ($translationOutput) { 
                    # If it had output, that's our translation result, and we can stop now.
                    $translationOutput
                    break
                }
            }
            "^$translationPrefix" {
                $translationMember = $definedLanguage.$_

                if ($translationMember -isnot [Management.Automation.PSMethodInfo]) {
                    switch -regex (@($translationMember.PSObject.Methods.Name)) {
                        "^$TranslationSuffix" {
                            $in = $_
                            $translationMember = $translationMember."$_"
                            break
                        }
                    }
                }
                
                if ($translationMember -is [Management.Automation.PSMethodInfo]) {
                    # If we found a matching translation method, run it
                    $translationOutput = $translationMember.Invoke($previousStatement)
                    if ($translationOutput) { 
                        # If it had output, that's our translation result, and we can stop now.
                        $translationOutput
                        break
                    }
                }
           
            }
            default {}
        }
    }

    # If we had a translation result,
    if ($translationResult) {
        # Output it, and attach the .ToRemove property, which will tell PipeScript to remove the previous statement.
        $translationResult | Add-Member ToRemove $previousStatement -Force -PassThru
    }
}