This is the mail archive of the
systemtap@sourceware.org
mailing list for the systemtap project.
[PATCH v2] Make the 3rd operand of ternary '?:' bind tighter than binary '='
- From: "Yichun Zhang (agentzh)" <agentzh at gmail dot com>
- To: systemtap at sourceware dot org
- Cc: "Yichun Zhang (agentzh)" <agentzh at gmail dot com>
- Date: Tue, 28 Aug 2018 10:26:01 -0700
- Subject: [PATCH v2] Make the 3rd operand of ternary '?:' bind tighter than binary '='
- References: <20180827230535.60969-1-agentzh@gmail.com>
In the C language, the 3rd operand of the ternary operator binds tighter
than the binary assignment operators. It is better for the stap language
to be consistent with C in operator precedence.
Added several tests to check the precedence of the ternary operator,
including nested ternary operator expressions. I've verified the results
with similar C programs with gcc myself.
Fixed an existing test case under systemtap.examples/, ansi_colors2.stp,
which incorrectly assumed that `+=` binds tighter than the 3rd operand
of the ternary operator.
The original behavior can be restored by the --compatible 3.3 option.
Updated NEWS to reflect this backward-incompatible change in the parser.
---
NEWS | 5 +
parse.cxx | 5 +-
testsuite/systemtap.base/ternary_op.exp | 303 +++++++++++++++++++++
testsuite/systemtap.base/ternary_op_1.stp | 4 +
testsuite/systemtap.base/ternary_op_2.stp | 5 +
testsuite/systemtap.base/ternary_op_3.stp | 5 +
testsuite/systemtap.base/ternary_op_6.stp | 4 +
testsuite/systemtap.base/ternary_op_7.stp | 10 +
.../systemtap.examples/general/ansi_colors2.stp | 2 +-
9 files changed, 341 insertions(+), 2 deletions(-)
create mode 100644 testsuite/systemtap.base/ternary_op.exp
create mode 100644 testsuite/systemtap.base/ternary_op_1.stp
create mode 100644 testsuite/systemtap.base/ternary_op_2.stp
create mode 100644 testsuite/systemtap.base/ternary_op_3.stp
create mode 100644 testsuite/systemtap.base/ternary_op_6.stp
create mode 100644 testsuite/systemtap.base/ternary_op_7.stp
diff --git a/NEWS b/NEWS
index 7ed02a99f..ab20986bf 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,11 @@
can now be stored in variables, passed as function arguments,
and stored as array keys and values.
+- The 3rd operand of the ternary operator '?:' in the script language
+ now binds tighter than the binary assignment operators like '=' and
+ '+=', just like the C language. The original operator precedence can
+ be restored by the '--compatible 3.3' option.
+
* What's new in version 3.3, 2018-06-08
- A new "stap --example FOO.stp" mode searches the example scripts
diff --git a/parse.cxx b/parse.cxx
index 751f56add..902feb64e 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -3286,7 +3286,10 @@ parser::parse_ternary ()
throw PARSE_ERROR (_("expected ':'"));
swallow ();
- e->falsevalue = parse_expression (); // XXX
+ if (input.has_version("4.0"))
+ e->falsevalue = parse_ternary ();
+ else
+ e->falsevalue = parse_assignment ();
return e;
}
else
diff --git a/testsuite/systemtap.base/ternary_op.exp b/testsuite/systemtap.base/ternary_op.exp
new file mode 100644
index 000000000..55262289e
--- /dev/null
+++ b/testsuite/systemtap.base/ternary_op.exp
@@ -0,0 +1,303 @@
+set test "ternary_op"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: ternary operator should be tighter than ,"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest1 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "positive 3\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+ if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+ }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: ternary operator should be tighter than = (bad case)"
+set test_name "$test: $subtest2"
+
+set cmd "stap '$srcdir/$subdir/${test}_2.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "^semantic error: Expecting lvalue for assignment: operator '=' at \[^\\n\]*?\\.stp:4:58\\n source: printf\\(\"a = %d, b = %d, c = %d\\\\n\", a > 0 \\? b = 4 : c = 5, b, c\\);\\n \\^\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -lineanchor -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: ternary operator should be tighter than = (bad case, compatible 4.0)"
+set test_name "$test: $subtest3"
+
+set cmd "stap --compatible 4.0 '$srcdir/$subdir/${test}_2.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "^semantic error: Expecting lvalue for assignment: operator '=' at \[^\\n\]*?\\.stp:4:58\\n source: printf\\(\"a = %d, b = %d, c = %d\\\\n\", a > 0 \\? b = 4 : c = 5, b, c\\);\\n \\^\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -lineanchor -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: ternary operator should be tighter than = (bad case, compatible 3.3)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest4 ($runtime)"
+
+ set cmd "stap --compatible 3.3 --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "a = 4, b = 4, c = 0\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+ if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+ }
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: ternary operator should be tighter than = (bad case, compatible 3.2)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest5 ($runtime)"
+
+ set cmd "stap --compatible 3.2 --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "a = 4, b = 4, c = 0\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+ if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+ }
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: ternary operator should be tighter than = (good case, with parens)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest6 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_6.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "3\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "^WARNING: Eliding assignment to 'b': operator '=' at \[^\\n\]*?\\.stp:3:30\n.*?\nWARNING: Eliding assignment to 'c': operator '=' at :3:39\n"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -lineanchor -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: nested ternary operators"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest7 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_7.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "3\n4\n6\n5\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+ if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+ }
+}
diff --git a/testsuite/systemtap.base/ternary_op_1.stp b/testsuite/systemtap.base/ternary_op_1.stp
new file mode 100644
index 000000000..ec9b697b4
--- /dev/null
+++ b/testsuite/systemtap.base/ternary_op_1.stp
@@ -0,0 +1,4 @@
+probe oneshot {
+ a = 3;
+ printf("%s %d\n", a > 0 ? "positive" : "negative", a);
+}
diff --git a/testsuite/systemtap.base/ternary_op_2.stp b/testsuite/systemtap.base/ternary_op_2.stp
new file mode 100644
index 000000000..374803003
--- /dev/null
+++ b/testsuite/systemtap.base/ternary_op_2.stp
@@ -0,0 +1,5 @@
+probe oneshot {
+ a = 3;
+ c = 0;
+ printf("a = %d, b = %d, c = %d\n", a > 0 ? b = 4 : c = 5, b, c);
+}
diff --git a/testsuite/systemtap.base/ternary_op_3.stp b/testsuite/systemtap.base/ternary_op_3.stp
new file mode 100644
index 000000000..8e6593b28
--- /dev/null
+++ b/testsuite/systemtap.base/ternary_op_3.stp
@@ -0,0 +1,5 @@
+probe begin {
+ a = 3;
+ printf("%d\n", a > 0 ? b = 3 : (c = 4));
+ exit();
+}
diff --git a/testsuite/systemtap.base/ternary_op_6.stp b/testsuite/systemtap.base/ternary_op_6.stp
new file mode 100644
index 000000000..c2a6bcd47
--- /dev/null
+++ b/testsuite/systemtap.base/ternary_op_6.stp
@@ -0,0 +1,4 @@
+probe oneshot {
+ a = 3;
+ printf("%d\n", a > 0 ? b = 3 : (c = 4));
+}
diff --git a/testsuite/systemtap.base/ternary_op_7.stp b/testsuite/systemtap.base/ternary_op_7.stp
new file mode 100644
index 000000000..248803703
--- /dev/null
+++ b/testsuite/systemtap.base/ternary_op_7.stp
@@ -0,0 +1,10 @@
+function test(a) {
+ printf("%d\n", a > 0 ? a > 1 ? 3 : 4 : a < -1 ? 5 : 6);
+}
+
+probe oneshot {
+ test(2);
+ test(1);
+ test(-1);
+ test(-2);
+}
diff --git a/testsuite/systemtap.examples/general/ansi_colors2.stp b/testsuite/systemtap.examples/general/ansi_colors2.stp
index 3ff3a16cd..64434e30c 100755
--- a/testsuite/systemtap.examples/general/ansi_colors2.stp
+++ b/testsuite/systemtap.examples/general/ansi_colors2.stp
@@ -18,7 +18,7 @@ probe begin {
for (r = 30; r < 38; r++)
# this displays more attributes
- for (t = 0; t < 8; !t ? ++t : t+=3) {
+ for (t = 0; t < 8; !t ? ++t : (t+=3)) {
printf(" %2d,%1d |", r, t);
for (c = 40; c < 48; c++) {
ansi_set_color(r, c, t)
--
2.11.0.295.gd7dffce