Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell's parsing modes: argument (command) mode vs. expression mode

Can anyone explain why when returning $false from a powershell function you are unable use a comparison operator to determine if the function has returned $false but when you return $true the comparison evaluates to $true?

function boolean {
    return $false
}

boolean -eq $false

function boolean {
    return $true
}

boolean -eq $true

>>>False
>>>True

You can work around this by setting the function call to a variable, but I was wondering if anyone could explain what is happening here under the hood?

function boolean {
    return $false
}

$bool = boolean 
$bool -eq $false

function boolean {
    return $true
}

$bool = boolean
$bool -eq $true

>>>True
>>>True
like image 730
Bob Avatar asked Feb 13 '18 21:02

Bob


1 Answers

PowerShell has two fundamental parsing modes:

  • argument mode, which works like traditional shells

    • In argument mode, the first token is interpreted as a command name (such as cmdlet name, function name, or filename of a script or external executable), followed by a whitespace-separated list of arguments.
  • expression mode, which works like traditional programming languages.

Running Get-help about_Parsing provides an introduction to these modes; in short, it is the first token that determines which mode is applied.
Also note that a given statement may be composed of parts that are parsed in either mode.


boolean -eq $false is parsed in argument mode, because its first token looks like a command name (an identifier that could be a program name, cmdlet name, function name, or alias).

Therefore, -eq and $false are interpreted as arguments (parameter values) to pass to function boolean.

Since your boolean functions are defined in a way that doesn't enforce passing values only to declared parameters, the arguments are effectively ignored, and the result of the statement is whatever the functions output ($false or $true).

As demonstrated in Mike Shepard's answer, you can make a function enforce use of only declared parameters (including none) with a param() block decorated with the [CmdletBinding()] attribute (which makes the function an advanced function), which would at least result in an error if you inadvertently passed arguments to the parameter-less boolean function.


You can work around this by setting the function call to a variable

$bool = boolean   # execute function and capture result in variable
$bool -eq $false  # use variable in the comparison 

The reason this works is that the -eq statement starts with $ - a variable reference in this case - which causes PowerShell to parse in expression mode, where -eq is recognized as an operator and $false as its RHS.

However, there is no need for this intermediate step:

To force a piece of code to be interpreted as an expression, enclose it in (...), the grouping operator:

(boolean) -eq $false # Calls function 'boolean' and uses result as LHS of -eq

(...) forces a new parsing context (which in itself is parsed in either argument or expression mode, again depending on the 1st token) and treats the result as an expression. which then allows its use as part of a larger expression, such as as an operand of the -eq operator, or as a command argument.

like image 187
mklement0 Avatar answered Sep 20 '22 09:09

mklement0