rfc:chaining_comparison

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
rfc:chaining_comparison [2016/12/12 17:57] bp1222rfc:chaining_comparison [2016/12/22 00:24] bp1222
Line 1: Line 1:
 ====== PHP RFC: Chaining Comparison ====== ====== PHP RFC: Chaining Comparison ======
-  * Version: 0.1 +  * Version: 0.2 
-  * Date: 2016-12-08 +  * Date: 2016-12-13 
-  * Author: David Walker (dave@mudsite.com)Richard Fussenegger (php@fleshgrinder.com)+  * Author: David Walker (dave@mudsite.com) 
 +  * Author: Richard Fussenegger (php@fleshgrinder.com)
   * Status: Draft   * Status: Draft
   * First Published at: http://wiki.php.net/rfc/chaining_comparison   * First Published at: http://wiki.php.net/rfc/chaining_comparison
  
 ===== Introduction ===== ===== Introduction =====
-The point of this RFC is to allow the chaining together of comparison and equality operations [''==, !=, !==, ===, <, <=, >, >=''to allow arbitrary comparisons.  The initial request that spawned this RFC was for interval checking.+This RFC proposes a syntax change to allow the chaining together of comparison and equality operations ''[==, !=, !==, ===, <, <=, >, >=]'' to allow arbitrary comparisons.  The initial request that spawned this RFC[1] was initially only for interval checking.  Discussion on the thread expanded the scope of the request to go from strictly interval checking to allowing more arbitrary number of comparisons.  It evolved from there to expand to a majority of the comparison operations.
  
-Today such comparisons must be written as such: 
 <file php> <file php>
 <?php <?php
 $a = 10; $a = 10;
  
 +/*
 + * The initial request of this proposal was to change the following syntax
 + */
 if (0 < $a && $a < 100) { if (0 < $a && $a < 100) {
     echo "Value is between 0 and 100\n";     echo "Value is between 0 and 100\n";
 } }
-</file> 
- 
-The proposal of this RFC is to allow new syntax as follows: 
-<file php> 
-<?php 
-$a = 10; 
  
 +/*
 + * To allow this to be functionally the same
 + */
 if (0 < $a < 100) { if (0 < $a < 100) {
     echo "Value is between 0 and 100\n";     echo "Value is between 0 and 100\n";
Line 30: Line 30:
  
 ===== Proposal ===== ===== Proposal =====
 +Proposals herein will contain a dump of relevant AST (php-ast) nodes and OPCodes (vld) to better visualize the compilation, and execution.
 ==== Comparison Chaining ==== ==== Comparison Chaining ====
-The proposal creates a new AST operation type ''ZEND_AST_COMPARE_OP'' which will be compiled in a left-precedence required manor.  In doing this compilation we introduce a new means of emitting an operation, by noting where a ''JMPZ_EX'' may need to exist, depending if we are continuing the compare chain.  This will shift operations that may have been emitted by compiling the right side of this AST compare to allow jumping over them if the left side of the operation is evaluated to false. +The proposal creates a new AST operation type ''ZEND_AST_COMPARE_OP'' which will be compiled in a left-recursive required manor.  In doing this compilation we ensure short cutting of righter operations if the left sides have evaluated to false.  To accomplish this we introduce a new means of emitting an operation, by noting where a ''JMPZ_EX'' may need to exist (see implementations for ''zend_emit_op_at'').  This will shift operations that may have been emitted by compiling the right side of this AST compare to allow jumping over them if the left side of the operation is evaluated to false.  I believe this means is necessary because we can't just shortcut if the left operation is false, ''false < $a++'' should still evaluate the right part of the expression.  We should only inject the JMPZ_EX ops, IF, the left child is a chained ''ZEND_AST_COMPARE_OP''.  The proposal also changes the associativity of the equality, and comparison, operations to being left associative.
- +
-The proposal also changes the precedence of the equality, and comparison, operations to being left recursive This is required, since if the left node of this operation is true, and it itself is a comparison operation, it should return the right node (for less than) to be used in the next comparison op.  Example: +
  
 <file php> <file php>
Line 67: Line 66:
     2        INIT_FCALL                                               'var_dump'     2        INIT_FCALL                                               'var_dump'
     3        IS_SMALLER                                       ~4      !0, 5     3        IS_SMALLER                                       ~4      !0, 5
-    4      > JMPZ_EX                                          ~     ~4, ->7+    4      > JMPZ_EX                                          ~     ~4, ->7
     5    >   POST_INC                                         ~5      !1     5    >   POST_INC                                         ~5      !1
-    6    >   IS_SMALLER                                       ~     ~4, ~5 +    6    >   IS_SMALLER                                       ~     ~4, ~5 
-    7    >   SEND_VAL                                                 ~6+    7    >   SEND_VAL                                                 ~4
     8        DO_ICALL                                                      8        DO_ICALL                                                 
  */  */
Line 112: Line 111:
  *  *
           INIT_FCALL                                               'var_dump'           INIT_FCALL                                               'var_dump'
-          IS_EQUAL                                         ~     !0, 1 +          IS_EQUAL                                         ~     !0, 1 
-        > JMPZ_EX                                          ~     ~4, ->6 +        > JMPZ_EX                                          ~     ~2, ->6 
-      >   IS_IDENTICAL                                     ~     ~4, <true> +      >   IS_IDENTICAL                                     ~     ~2, <true> 
-      >   SEND_VAL                                                 ~5+      >   SEND_VAL                                                 ~2
  */  */
 </file> </file>
  
-==== Combined Uses (Open Discussion Topic)==== +===== Backward Incompatible Changes ===== 
-One of the concerns raised in the implementation of this feature would be how to handle expressions such as ''1 < 2 == 3 < 4''.  This is valid syntax in PHP 7.1 and before that essentially checks if true == true.  The current implementation respects the current syntaxby evaluating the comparison operators before equality operators.  Combining the two chaining methods we can write a new expression like this example:+BC Breaking changes expected depending on open-issue answers 
 + 
 +===== Proposed PHP Version(s===== 
 +Next PHP (currently 7.2) 
 + 
 +===== RFC Impact ===== 
 +==== To Opcache ==== 
 +Yes, we're adding new JMPZ_EX codes when chaining to ensure false values correctly jump over any pre/post inc/dev ops from eval. 
 + 
 +===== Open Issues ===== 
 +====Should equality and comparison expressions be treated as same precedence?==== 
 +This is harder of a question that it seems.  What we are asking is how should we parse a seemingly simple expression: 
 +''1 < 2 == 3 < 4'' 
 + 
 +Why is this even a question, much less a challenging one?  Well, a seemingly majority of languages ''[C[2], C++[3], Java[4], Ruby[5], Perl[6]]'' all would tell you that the expression would evaluate to true.  However some, like ''Python[7]'', would evaluate that expression to false Some, like ''[Numbers, LibreOffice]'' will raise a syntax error, or give awkward answers.  The question we have is which way should PHP go with the evaluation of this expression?  Clearly we can ascertain that the true-evaluating languages have the precedence of the less-than operator more imporatant than that of the equality, so they check if true == true.  Whereas the false-evaluating languages treat comparisons and equality with the same precedence.  As such they compare 1 less than 2, 2 is-equal 3.  The latter group are apparently more strictly typed and won't compare bools to numbers, but even there we can see the precedence is equalas it's comparing the result of the first expression into the next ''(1 < 2) == 3'' 
 + 
 +It is important to point out that the example syntax is currently valid in PHP 7.1.  PHP 7.1 currently has a C-like precedence where ''[<, <=, >, >=]'' are a higher precedence than ''[==, !=, ===, !==]''[8].  Below are expressions and their return values in PHP 7.1, and with the two potential methods of evaluating that expression.
  
 <file php> <file php>
 <?php <?php
-$a = 1; 
-$b = 4; 
-$c = 10; 
  
-var_dump($a < 2 == 3 < $b < 5 == 20 > $c); // bool(true)+/* 
 + * PHP <= 7.1 
 + */ 
 +var_dump(< 2 == 3 < 4); // bool(true) 
 +var_dump(1 < 2 == 3 < 4 == 5 < 6) // Syntax Error
  
 /* /*
- AST Dump + Proposed Chaining, comparators evaluated first; equality second [SeeImplementation #1]
- * +
-  3AST_CALL +
-       expr: AST_NAME +
-           flags: NAME_NOT_FQ (1+
-           name: "var_dump" +
-       args: AST_ARG_LIST +
-           0: AST_COMPARE_OP +
-               flags: COMPARE_IS_EQUAL (17) +
-               left: AST_COMPARE_OP +
-                   flags: COMPARE_IS_EQUAL (17) +
-                   left: AST_COMPARE_OP +
-                       flags: COMPARE_IS_SMALLER (19) +
-                       left: AST_VAR +
-                           name: "a" +
-                       right:+
-                   right: AST_COMPARE_OP +
-                       flags: COMPARE_IS_SMALLER (19) +
-                       left: AST_COMPARE_OP +
-                           flags: COMPARE_IS_SMALLER (19) +
-                           left: 3 +
-                           right: AST_VAR +
-                               name: "b" +
-                       right:+
-               right: AST_COMPARE_OP +
-                   flags: COMPARE_IS_SMALLER (19) +
-                   left: 20 +
-                   right: AST_VAR +
-                       name: "c"+
  */  */
 +var_dump(1 < 2 == 3 < 4); // bool(true)
 +var_dump(1 < 2 == 3 < 4 == 5 < 6) // bool(true)
  
 /* /*
- OPCodes + Proposed Strict Chaining [See: Implementation #2]
- * +
-          INIT_FCALL                                               'var_dump' +
-          IS_SMALLER                                       ~6      !0, 2 +
-        > JMPZ_EX                                          ~9      ~6, ->10 +
-      >   IS_SMALLER                                       ~7      3, !1 +
-        > JMPZ_EX                                          ~8      ~7, ->9 +
-      >   IS_SMALLER                                       ~8      ~7, 5 +
-      >   IS_EQUAL                                         ~9      ~6, ~8 +
-  10    > > JMPZ_EX                                          ~11     ~9, ->13 +
-  11    >   IS_SMALLER                                       ~10     !2, 20 +
-  12        IS_EQUAL                                         ~11     ~9, ~10 +
-  13    >   SEND_VAL                                                 ~11 +
-  14        DO_ICALL                                                 +
  */  */
 +var_dump(1 < 2 == 3 < 4); // bool(false)
 +var_dump(1 < 2 == 3 < 4 == 5 < 6) // bool(false)
 +var_dump((1 < 2) == (3 < 4) == (5 < 6)) // bool(true)
 </file> </file>
  
-===== Backward Incompatible Changes ===== +==== Right Recursion ==== 
-No BC Breaking changes expected+Another syntax difference that could be BC problematic is with right-recursion of the chained expression.  Currently PHP will evaluate right recursive single expression comparisons.  The proposed feature would raise a compile time error doing this.  The example being:
  
-===== Proposed PHP Version(s) ===== +<file php> 
-Next PHP (currently 7.2)+<?php
  
-===== RFC Impact ===== +/* 
-==== To Opcache ==== + * PHP <7.1 
-Yes, we're adding new JMPZ_EX codes when chaining to ensure false values correctly jump over any pre/post inc/dev ops from eval.+ */ 
 +var_dump(1 < (2 < 3); // bool(false) - in short it simplifies:  1 < (true) :false 
 +var_dump(1 < 2 == 3); // bool(true) 
 +var_dump(1 < 2 == == 4); // Parse Error 
 +var_dump(1 < 2 == (3 == 4)); //bool(false)
  
-===== Open Issues ====+/* 
-====Should equality and comparison expressions be treated as same precedence?==== + * Proposed Chaining 
-This is a tough question.  It's essentially asking if an expression like ''1 < 2 == 3 < 4'' should be evaluated as ''(1 < 2== (3 4)'' OR ''(1 < 2&& (== 3) && (3 < 4)''.+ */ 
 +var_dump(1 < (2 <3)); // Compile Error 
 +var_dump(1 < 2 == == 4); // bool(true) - evaluated: true == == 4 :true == 4 :true 
 +var_dump(1 < 2 == (3 == 4)); //bool(false) - evaluated true == false := false
  
-Why is this even a question?  A majority of languages would evaluate the expression with the former interpretation However, Python does the latter The latter does enforce more of a "chaining" of these comparisons During discussionthis question should be addressed, as the underlying code to support either means is very minor.+/* 
 + * Possible change allowing right-recursive chaining 
 + */ 
 +var_dump(1 < (2 < 3)); // bool(true) - evaluated as: 1 && (2 < 3) && (1 < 2) := true && true && true := true 
 + 
 +</file> 
 + 
 +The potential fix, is evaluated as such.  If it looks odd with the ''1 && ...'' that's because we evaluate left to rightand the left side there could have been ''$a++'', we want to ensure the post-inc operator is correctly run as we do evaluate left-to-right.
  
 ===== Unaffected PHP Functionality ===== ===== Unaffected PHP Functionality =====
 Does not alter the operation of the comparison Spaceship [<=>] operator. Does not alter the operation of the comparison Spaceship [<=>] operator.
- 
-===== Future Scope ===== 
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
Line 205: Line 197:
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
-A Proposed Implementaiton: https://github.com/php/php-src/compare/master...bp1222:multi-compare+Implementation #1: comparisons evaluated before equality: https://github.com/php/php-src/compare/master...bp1222:multi-compare 
 + 
 +Implementation #2: comparisons and equality evaluated together: https://github.com/php/php-src/compare/master...bp1222:multi-compare-equal-prec
  
 Will need eyes of those more familiar with AST/VM to review. Will need eyes of those more familiar with AST/VM to review.
Line 215: Line 209:
  
 ===== References ===== ===== References =====
-Initial idea on Internals: http://marc.info/?l=php-internals&m=147846422102802&w=2+  * [1] - [[http://marc.info/?l=php-internals&m=147846422102802&w=2|Initial idea on Internals]] 
 +  * [2] - [[https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#Operator-Precedence|Precedence in C]] 
 +  * [3] - [[https://msdn.microsoft.com/en-us/library/126fe14k.aspx|Precedence in C++]] 
 +  * [4] - [[http://introcs.cs.princeton.edu/java/11precedence/|Precedence in Java]] 
 +  * [5] - [[https://ruby-doc.org/core-2.2.0/doc/syntax/precedence_rdoc.html|Precedence in Ruby]] 
 +  * [6] - [[http://perldoc.perl.org/perlop.html#Operator-Precedence-and-Associativity|Precedence in Perl]] 
 +  * [7] - [[https://docs.python.org/2/reference/expressions.html#operator-precedence|Precedence in Python]] 
 +  * [8] - [[http://php.net/manual/fa/language.operators.precedence.php|Precedence in PHP]] 
  
 ===== Rejected Features ===== ===== Rejected Features =====
 Keep this updated with features that were discussed on the mail lists. Keep this updated with features that were discussed on the mail lists.
rfc/chaining_comparison.txt · Last modified: 2021/03/27 14:58 by ilutov