Operator precedence
lexidor opened this issue · comments
Please complete the information below:
Where is the problem?
In the operator precedence table, &&
beats |
.
What is the problem?
Would you happen to know if the operator precedence table is correct? I believe it not to be.
- Given the expression
0 | 0 && 0
- The precedence rules from the docs state:
&&
has a higher precedence than|
- So you would expect the expression
0 | (0 && 0)
to be the same - Evaluating
0 | 0 && 0
results infalse
- Evaluating
0 | (0 && 0)
results inInvalidOperationException
This leads me to believe that 0 | 0 && 0
is parsed as (0 | 0) && 0
, and not 0 | (0 && 0)
.
Please don't change anything below this point.
- Build ID: HHVM=HHVM-4.154.0:HSL=v4.108.1:2022-05-03T20:54:00+0000:1fa47f258c6b68f8ec01899aa82fd6ffa0957109
- Page requested: /hack/expressions-and-operators/operator-precedence
- Page requested at: Sat, 07 May 2022 17:52:43 +0000
- Controller: GuidePageController
@lexidor this does seem like the ordering is off. I'm going to look into this and also the ordering of other things in the table while I'm digging into the details. Give me a day or so and I'll report back.
Reminders:
- Add a note that a single bar (
|
) is a bitwise operator and a double-bar (||
) is the boolean operator analog to the double ampersand (&&
).
Looks like this may be the source of truth: https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs
Thanks for digging into this. The code confirms, the ordering of the table is off. How should I proceed? This table is missing many operators, like readonly
. Would a manual fix of this table be good enough or should I extract this information and generate this table?
If you're able to do that programmatically that would be awesome: I suspect ordering won't change but new operators like readonly
will be missed if we keep this as a manual process. At the moment I'm still confirming behavior. Did you see which enums correspond to |
and &&
?
Yes, I inferred the order here.
The order goes (from weak to strong / low to high precedence) ||
&&
|
^
&
.
If you're able to do that programmatically that would be awesome: I suspect ordering won't change but new operators like
readonly
will be missed if we keep this as a manual process.
curl --silent https://raw.githubusercontent.com/facebook/hhvm/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs | hhvm extr4ct0r.hack
DollarOperator | right
----------------------------------------
ScopeResolutionOperator | left
----------------------------------------
IndexingOperator | left
----------------------------------------
MemberSelectionOperator | left
NullSafeMemberSelectionOperator | left
----------------------------------------
NewOperator | none
----------------------------------------
EnumClassLabelOperator | left
FunctionCallOperator | left
----------------------------------------
CloneOperator | none
----------------------------------------
PostfixIncrementOperator | right
PostfixDecrementOperator | right
AwaitOperator | right
ReadonlyOperator | right
----------------------------------------
CastOperator | right
ErrorControlOperator | right
PrefixIncrementOperator | right
PrefixDecrementOperator | right
ExponentOperator | right
----------------------------------------
InstanceofOperator | none
IsOperator | left
AsOperator | left
NullableAsOperator | left
UpcastOperator | left
----------------------------------------
LogicalNotOperator | right
NotOperator | right
UnaryPlusOperator | right
UnaryMinusOperator | right
----------------------------------------
MultiplicationOperator | left
DivisionOperator | left
RemainderOperator | left
----------------------------------------
AdditionOperator | left
SubtractionOperator | left
ConcatenationOperator | left
----------------------------------------
LeftShiftOperator | left
RightShiftOperator | left
----------------------------------------
SpaceshipOperator | none
LessThanOperator | none
LessThanOrEqualOperator | none
GreaterThanOperator | none
GreaterThanOrEqualOperator | none
----------------------------------------
EqualOperator | none
StrictEqualOperator | none
NotEqualOperator | none
StrictNotEqualOperator | none
----------------------------------------
AndOperator | left
----------------------------------------
ExclusiveOrOperator | left
----------------------------------------
OrOperator | left
----------------------------------------
LogicalAndOperator | left
----------------------------------------
LogicalOrOperator | left
----------------------------------------
CoalesceOperator | right
----------------------------------------
ConditionalQuestionOperator | left
ConditionalColonOperator | left
DegenerateConditionalOperator | left
----------------------------------------
PipeOperator | left
----------------------------------------
AssignmentOperator | right
AdditionAssignmentOperator | right
SubtractionAssignmentOperator | right
MultiplicationAssignmentOperator | right
DivisionAssignmentOperator | right
ExponentiationAssignmentOperator | right
RemainderAssignmentOperator | right
ConcatenationAssignmentOperator | right
AndAssignmentOperator | right
OrAssignmentOperator | right
ExclusiveOrAssignmentOperator | right
LeftShiftAssignmentOperator | right
RightShiftAssignmentOperator | right
CoalesceAssignmentOperator | right
----------------------------------------
PrintOperator | right
----------------------------------------
IncludeOperator | left
IncludeOnceOperator | left
RequireOperator | left
RequireOnceOperator | left
----------------------------------------
namespace Extr4ct0r;
use namespace HH\Lib\{C, Dict, File, Keyset, IO, Regex, Str, Vec};
// @see https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs#L27
const string PRECEDENCE_FUNC_HEADER =
'pub fn precedence(&self, _: &ParserEnv) -> usize {';
// @see https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs#L101
const string ASSOC_FUNC_HEADER =
'pub fn associativity(&self, _: &ParserEnv) -> Assoc {';
// @see https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs#L34
// @see https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs#L102
const string MATCH_SELF = 'match self {';
// @see https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs#L15-L19
enum Assoc: string {
LEFT = 'LeftAssociative';
NON = 'NotAssociative';
RIGHT = 'RightAssociative';
}
function extract_first_match_block_of_func(
string $source,
string $function_header,
)[]: string {
// Stripping comments
$source = Regex\replace($source, re'#//.+\n#', '');
// Finding the start of the function
$prefix_pos = Str\search($source, $function_header);
invariant($prefix_pos is nonnull, 'could not find "%s"', $function_header);
// Find the first match block after the function header
$match_pos = Str\search($source, MATCH_SELF, $prefix_pos);
invariant($match_pos is nonnull, 'could not find match block');
// Slice right after the `{` of `match self {`
$match_block = Str\slice($source, $match_pos + Str\length(MATCH_SELF));
// Increment `$ii` until we find the accompanying `}`
for ($curly_count = 1, $ii = 0; $curly_count !== 0; ++$ii) {
$char = $match_block[$ii];
if ($char === '{') {
$curly_count++;
} else if ($char === '}') {
$curly_count--;
}
}
// Stripping all whitespace (including spaces between tokens) from the match body
return Str\slice($match_block, 0, $ii - 1) |> Regex\replace($$, re'/\s/', '');
}
function extract_precedence(string $source)[]: vec<vec<string>> {
return extract_first_match_block_of_func($source, PRECEDENCE_FUNC_HEADER)
// `=> { 21 }` is an alternative notation for `=> 21,`
|> Regex\replace($$, re'/{\d+}/', ',')
// Match arms are separated by a comma, split the input so each value is one arm
|> Str\split($$, ',')
// The LHS of each arm is a series of names delimited by `|`, the text after the `=>` can be ignored
|> Vec\map(
$$,
$l ==> Str\slice($l, 0, Str\search($l, '=>')) |> Str\split($$, '|'),
)
// The operation above yields one bad result `vec['']`, which we remove here
|> Vec\filter($$, $v ==> $v !== vec[''])
// The order in the source file is from low to high, reverse that order
|> Vec\reverse($$);
}
function extract_associativity(string $source)[]: dict<Assoc, keyset<string>> {
return extract_first_match_block_of_func($source, ASSOC_FUNC_HEADER)
// Match arms are separated by a comma, split the input so each value is one arm
|> Str\split($$, ',')
// This match has a trailing comma, so we have a dangling "" to remove
|> Vec\filter($$, $l ==> $l !== '')
// The LHS of each arm is a series of names delimited by `|` followed by `=>` and an associativity
|> Vec\map($$, $l ==> Str\split($l, '=>') as (string, string))
|> Dict\pull(
$$,
$x ==> keyset(Str\split($x[0], '|')),
$x ==> Regex\first_match($x[1], re'/ssoc::(\w+)/') as nonnull[1] as Assoc,
);
}
<<__EntryPoint>>
async function main_async(): Awaitable<void> {
$operator_rs = await IO\request_input()->readAllAsync(null, 1_000_000_000);
$precedence = extract_precedence($operator_rs);
$associativity = extract_associativity($operator_rs);
// hackfmt-ignore
$assoc_name = $op ==> C\contains_key($associativity[Assoc::LEFT], $op) ? 'left'
: (C\contains_key($associativity[Assoc::RIGHT], $op) ? 'right'
: (C\contains_key($associativity[Assoc::NON], $op) ? 'none' : 'unknown'));
foreach ($precedence as $same_level) {
foreach ($same_level as $operator) {
echo Str\format("%32s | %s\n", $operator, $assoc_name($operator));
}
echo Str\repeat('-', 40)."\n";
}
}