summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2019-04-23 13:14:27 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2019-06-13 18:44:32 +0900
commitf169043d81524b5b529f2c1e9c35437ba5bc3a7a (patch)
treecaa8e094163afc9d2d177cc0d151a8b906704aee
parente717d6faa8463c70407e6aaf116c6b6181f30be6 (diff)
Add pipeline operator [Feature #15799]
-rw-r--r--NEWS12
-rw-r--r--defs/id.def1
-rw-r--r--ext/ripper/eventids2.c1
-rw-r--r--parse.y25
-rw-r--r--test/ripper/test_scanner_events.rb2
-rw-r--r--test/ruby/test_syntax.rb7
6 files changed, 47 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index 4ad38c9f12..a332cf389a 100644
--- a/NEWS
+++ b/NEWS
@@ -48,6 +48,18 @@ sufficient information, see the ChangeLog file or Redmine
" # This has been warned since 2.4
EOS
+* Pipeline operator is added.
+
+ This code equals to the next code.
+
+ foo()
+ |> bar 1, 2
+ |> display
+
+ foo()
+ .bar(1, 2)
+ .display
+
=== Core classes updates (outstanding ones only)
Enumerable::
diff --git a/defs/id.def b/defs/id.def
index 44890cf352..66dfdf915a 100644
--- a/defs/id.def
+++ b/defs/id.def
@@ -111,6 +111,7 @@ token_ops = %[\
OROP ||
ANDDOT &.
METHREF .:
+ PIPE |>
]
class KeywordError < RuntimeError
diff --git a/ext/ripper/eventids2.c b/ext/ripper/eventids2.c
index 88985accd9..66e7c8fb3f 100644
--- a/ext/ripper/eventids2.c
+++ b/ext/ripper/eventids2.c
@@ -262,6 +262,7 @@ static const struct token_assoc {
{tDSTAR, O(op)},
{tANDDOT, O(op)},
{tMETHREF, O(op)},
+ {tPIPE, O(op)},
{tSTRING_BEG, O(tstring_beg)},
{tSTRING_CONTENT, O(tstring_content)},
{tSTRING_DBEG, O(embexpr_beg)},
diff --git a/parse.y b/parse.y
index c78f5139f7..6f282d8014 100644
--- a/parse.y
+++ b/parse.y
@@ -1002,7 +1002,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%type <node> literal numeric simple_numeric ssym dsym symbol cpath
%type <node> top_compstmt top_stmts top_stmt begin_block
%type <node> bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call
-%type <node> expr_value expr_value_do arg_value primary_value fcall rel_expr
+%type <node> expr_value expr_value_do arg_value primary_value fcall rel_expr pipeline
%type <node> if_tail opt_else case_body case_args cases opt_rescue exc_list exc_var opt_ensure
%type <node> args call_args opt_call_args
%type <node> paren_args opt_paren_args args_tail opt_args_tail block_args_tail opt_block_args_tail
@@ -1060,6 +1060,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%token <id> tANDDOT RUBY_TOKEN(ANDDOT) "&."
%token <id> tCOLON2 RUBY_TOKEN(COLON2) "::"
%token <id> tMETHREF RUBY_TOKEN(METHREF) ".:"
+%token tPIPE RUBY_TOKEN(PIPE) "|>"
%token tCOLON3 ":: at EXPR_BEG"
%token <id> tOP_ASGN "operator-assignment" /* +=, -= etc. */
%token tASSOC "=>"
@@ -1095,6 +1096,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%nonassoc modifier_if modifier_unless modifier_while modifier_until
%left keyword_or keyword_and
%right keyword_not
+%left tPIPE
%nonassoc keyword_defined
%right '=' tOP_ASGN
%left modifier_rescue
@@ -2269,12 +2271,29 @@ arg : lhs '=' arg_rhs
/*% %*/
/*% ripper: ifop!($1, $3, $6) %*/
}
+ | pipeline
| primary
{
$$ = $1;
}
;
+pipeline : arg tPIPE operation2 opt_paren_args
+ {
+ /*%%%*/
+ $$ = new_command_qcall(p, ID2VAL(idPIPE), $1, $3, $4, Qnull, &@3, &@$);
+ /*% %*/
+ /*% ripper: command_call!($1, ID2VAL(idPIPE), $3, $4) %*/
+ }
+ | arg tPIPE operation2 opt_paren_args brace_block
+ {
+ /*%%%*/
+ $$ = new_command_qcall(p, ID2VAL(idPIPE), $1, $3, $4, $5, &@3, &@$);
+ /*% %*/
+ /*% ripper: method_add_block!(command_call!($1, ID2VAL(idPIPE), $3, $4), $5) %*/
+ }
+ ;
+
relop : '>' {$$ = '>';}
| '<' {$$ = '<';}
| tGEQ {$$ = idGE;}
@@ -8924,6 +8943,10 @@ parser_yylex(struct parser_params *p)
SET_LEX_STATE(EXPR_BEG);
return tOP_ASGN;
}
+ if (c == '>') {
+ SET_LEX_STATE(EXPR_DOT);
+ return tPIPE;
+ }
SET_LEX_STATE(IS_AFTER_OPERATOR() ? EXPR_ARG : EXPR_BEG|EXPR_LABEL);
pushback(p, c);
return '|';
diff --git a/test/ripper/test_scanner_events.rb b/test/ripper/test_scanner_events.rb
index 339a66d5ec..323c4098e8 100644
--- a/test/ripper/test_scanner_events.rb
+++ b/test/ripper/test_scanner_events.rb
@@ -573,6 +573,8 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase
scan('op', 'obj.:foo')
assert_equal [],
scan('op', %q[`make all`])
+ assert_equal %w[|>],
+ scan('op', %q[x|>y])
end
def test_symbeg
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index f2fcf671aa..7bffb8790b 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -1379,6 +1379,13 @@ eom
assert_syntax_error('@1', /outside block/)
end
+ def test_pipeline_operator
+ assert_valid_syntax('x |> y')
+ x = nil
+ assert_equal("121", eval('x = 12 |> pow(2) |> to_s(11)'))
+ assert_equal(12, x)
+ end
+
private
def not_label(x) @result = x; @not_label ||= nil end