Transpilers/Core/Pipescript.FunctionDefinition.psx.ps1

<#
.SYNOPSIS
    PipeScript Function Transpiler
.DESCRIPTION
    Transpiles Function Definitions.
#>

param(
# An abstract syntax tree function definition.
[Parameter(Mandatory,ParameterSetName='FunctionAst',ValueFromPipeline)]
[Management.Automation.Language.FunctionDefinitionAst]
$FunctionDefinition
)

process {
    $TranspilerSteps = @()
    $realFunctionName = $functionDefinition.Name
    if ($FunctionDefinition.Name -match '\W(?<Name>\w+)$' -or
        $FunctionDefinition.Name -match '^(?<Name>[\w-]+)\W') { 
        
        $TranspilerSteps = @([Regex]::new('
            ^\s{0,}
            (?<BalancedBrackets>
            \[ # An open bracket
            (?> # Followed by...
                [^\[\]]+| # any number of non-bracket character OR
                \[(?<Depth>)| # an open bracket (in which case increment depth) OR
                \](?<-Depth>) # a closed bracket (in which case decrement depth)
            )*(?(Depth)(?!)) # until depth is 0.
            \] # followed by a closing bracket
            ){0,}
        '
, 'IgnoreCase,IgnorePatternWhitespace','00:00:00.1').Match($FunctionDefinition.Name).Groups["BalancedBrackets"])

        if ($TranspilerSteps ) {
            $transpilerStepsEnd     = $TranspilerSteps[-1].Start + $transpilerSteps[-1].Length                
            $realFunctionName       = $functionDefinition.Name.Substring($transpilerStepsEnd)
        }
    }

    $inlineParameters =
        if ($FunctionDefinition.Parameters) {
            "($($FunctionDefinition.Parameters.Transpile() -join ','))"
        } else {
            ''
        }
    $partialCommands = 
        @(
        if ($realFunctionName -notmatch 'partial\p{P}') {
            if (-not $script:PartialCommands) {
                $script:PartialCommands =
                $ExecutionContext.SessionState.InvokeCommand.GetCommands('Partial*',
                    'Function,Alias',$true)
            }
    
            $partialCommands = @(foreach ($partialFunction in $script:PartialCommands) {
                # Only real partials should be considered.

                if ($partialFunction -notmatch  'partial\p{P}') { continue }
                # Partials should not combine with other partials.
                
                $partialName = $partialFunction.Name -replace '^Partial\p{P}'
                if (
                    (
                        # If there's a slash in the name, treat it as a regex
                        $partialName -match '/' -and
                        $realFunctionName -match ($partialName -replace '/')
                    ) -or (
                        # If there's a slash * or ?, treat it as a wildcard
                        $partialName -match '[\*\?]' -and
                        $realFunctionName -like $partialName
                    ) -or (
                        # otherwise, treat it as an exact match.
                        $realFunctionName -eq $partialName
                    )
                ) {
                    $partialFunction
                }
            })

            # If there were any partial commands
            if ($partialCommands) {
                # sort them by rank and name.
                $partialCommands | Sort-Object Rank, Name
            }
        })    
    
    $newFunction = @(
        if ($FunctionDefinition.IsFilter) {
            "filter", $realFunctionName, $inlineParameters, '{' -ne '' -join ' '
        } else {
            "function", $realFunctionName, $inlineParameters, '{' -ne '' -join ' '
        }
        # containing the transpiled funciton body.
        $transpiledFunctionBody = [ScriptBlock]::Create(($functionDefinition.Body.Extent -replace '^{' -replace '}$')) |
            .>Pipescript -Transpiler $transpilerSteps
        
        # If there were not partial commands, life is easy, we just return the transpiled ScriptBlock
        if (-not $partialCommands) {
            $transpiledFunctionBody
        } else {
            # If there were any partial commands,
            @(                
                $transpiledFunctionBody # we join them with the transpiled code.
                $alreadyIncluded = [Ordered]@{} # Keep track of what we've included.
                foreach ($partialCommand in $partialCommands) { # and go over each partial command
                    if ($alreadyIncluded["$partialCommand"]) { continue }
                    # and get it's ScriptBlock
                    if ($partialCommand.ScriptBlock) {
                        $partialCommand.ScriptBLock
                    } elseif ($partialCommand.ResolvedCommand) {
                        # (if it's an Alias, keep resolving until we can't resolve anymore).
                        $resolvedAlias = $partialCommand.ResolvedCommand
                        while ($resolvedAlias -is [Management.Automation.AliasInfo]) {
                            $resolvedAlias = $resolvedAlias.ResolvedCommand
                        }
                        if ($resolvedAlias.ScriptBlock) {
                            $resolvedAlias.ScriptBlock
                        }                        
                    }
                    # Then mark the command as included, just in case.
                    $alreadyIncluded["$partialCommand"] = $true
                }
            ) | # Take all of the combined input and pipe in into Join-PipeScript
                Join-PipeScript -Transpile
        }
        "}"
    )
    # Create a new script block
    $transpiledFunction = [ScriptBlock]::Create($newFunction -join [Environment]::NewLine)

    Import-PipeScript -ScriptBlock $transpiledFunction -NoTranspile
    # Create an event indicating that a function has been transpiled.
    $null = New-Event -SourceIdentifier PipeScript.Function.Transpiled -MessageData ([PSCustomObject][Ordered]@{
        PSTypeName = 'PipeScript.Function.Transpiled'
        FunctionDefinition = $FunctionDefinition
        ScriptBlock = $transpiledFunction
    })

    # Output the transpiled function.
    $transpiledFunction
}