hhvm / user-documentation

Documentation for those that use HHVM and write Hack code.

Home Page:http://docs.hhvm.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 in false
  • Evaluating 0 | (0 && 0) results in InvalidOperationException

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 (&&).

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.

https://github.com/facebook/hhvm/blob/bd90d27245b44a08269307dbc3da589de0f138f1/hphp/hack/src/parser/operator.rs#L56-L60

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";
  }
}