about_Foreach example: cmd subroutine syntax in PowerShell?

189 Views Asked by At

In this about page, there is a code block (below) that shows off the $ForEach automatic variable, but it also has syntax like a subroutine in batch. I cannot find documentation on how this piece of code functions or what the language construct is called. I believe it's a PowerShell v5 addition, but reading the release notes didn't help me either. What does :tokenLoop foreach ($token in $tokens) represent?

function Get-FunctionPosition {
  [CmdletBinding()]
  [OutputType('FunctionPosition')]
  param(
    [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [Alias('PSPath')]
    [System.String[]]
    $Path
  )

  process {
    try {
      $filesToProcess = if ($_ -is [System.IO.FileSystemInfo]) {
        $_
      }
      else {
        Get-Item -Path $Path
      }
      foreach ($item in $filesToProcess) {
        if ($item.PSIsContainer -or $item.Extension -notin @('.ps1', '.psm1')) {
          continue
        }
        $tokens = $errors = $null
        $ast = [System.Management.Automation.Language.Parser]::ParseFile($item.FullName, ([REF]$tokens), ([REF]$errors))
        if ($errors) {
          Write-Warning "File '$($item.FullName)' has $($errors.Count) parser errors."
        }
        :tokenLoop foreach ($token in $tokens) {
          if ($token.Kind -ne 'Function') {
              continue
          }
          $position = $token.Extent.StartLineNumber
          do {
            if (-not $foreach.MoveNext()) {
              break tokenLoop
            }
            $token = $foreach.Current
          } until ($token.Kind -in @('Generic', 'Identifier'))
          $functionPosition = [pscustomobject]@{
            Name       = $token.Text
            LineNumber = $position
            Path       = $item.FullName
          }
          Add-Member -InputObject $functionPosition -TypeName FunctionPosition -PassThru
        }
      }
    }
    catch {
      throw
    }
  }
}
1

There are 1 best solutions below

1
On BEST ANSWER

In PowerShell version 3.0 and up (at least since version 2.0), the following statement types are optionally labelled:

  • switch
  • foreach
  • for
  • while
  • do

Now, what does this mean? It means that you can supply a label name as an argument to break or continue statement inside the body of your labeled statement and have the flow control apply to the statement indicated by the label.

Consider this example:

foreach($Name in 'Alice','Bob','Charlie'){
    switch($Name.Length){
        {$_ -lt 4} {
            # We don't have time for short names, go to the next person
            continue
        }
        default {
            Write-Host "$Name! What a beautiful name!"
        }
    }

    Write-Host "Let's process $Name's data!"
}

You might expect the "Let's process [...]" string to appear only twice, since we continue in the case of Bob, but since the immediate parent statement is a switch, it didn't actually apply to the foreach statement.

Now, if we can explicitly state that we want to continue the foreach loop rather than the switch statement, we can avoid that:

:outer_loop
foreach($Name in 'Alice','Bob','Charlie'){
    switch($Name.Length){
        {$_ -lt 4} {
            # We don't have time for short names, go to the next person
            continue outer_loop
        }
        default {
            Write-Host "$Name! What a beautiful name!"
        }
    }

    Write-Host "Let's process $Name's data!"
}

Now the continue statement actually continues the loop rather than the switch.

Quite useful when you have nested loop constructs.


Labels are briefly discussed in conjunction with break inside a while statement in the about_Break help topic