Safe navigation operator
* compile.c (iseq_peephole_optimize): peephole optimization for branchnil jumps. * compile.c (iseq_compile_each): generate save navigation operator code. * insns.def (branchnil): new opcode to pop the tos and branch if it is nil. * parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'. [Feature #11537] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52214 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
5a599dde0c
commit
a356fe1c35
14
ChangeLog
14
ChangeLog
@ -1,3 +1,17 @@
|
|||||||
|
Thu Oct 22 15:30:08 2015 Nobuyoshi Nakada <nobu@ruby-lang.org>
|
||||||
|
|
||||||
|
* compile.c (iseq_peephole_optimize): peephole optimization for
|
||||||
|
branchnil jumps.
|
||||||
|
|
||||||
|
* compile.c (iseq_compile_each): generate save navigation operator
|
||||||
|
code.
|
||||||
|
|
||||||
|
* insns.def (branchnil): new opcode to pop the tos and branch if
|
||||||
|
it is nil.
|
||||||
|
|
||||||
|
* parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'.
|
||||||
|
[Feature #11537]
|
||||||
|
|
||||||
Thu Oct 22 13:16:19 2015 Guilherme Reis Campos <guilhermekbsa@gmail.com>
|
Thu Oct 22 13:16:19 2015 Guilherme Reis Campos <guilhermekbsa@gmail.com>
|
||||||
|
|
||||||
* dir.c (ruby_brace_expand): glob brace expansion edge case fix.
|
* dir.c (ruby_brace_expand): glob brace expansion edge case fix.
|
||||||
|
11
NEWS
11
NEWS
@ -18,6 +18,17 @@ with all sufficient information, see the ChangeLog file.
|
|||||||
* besides, --enable/--disable=frozen-string-literal options also have
|
* besides, --enable/--disable=frozen-string-literal options also have
|
||||||
been introduced.
|
been introduced.
|
||||||
|
|
||||||
|
* safe navigation operator:
|
||||||
|
|
||||||
|
* new method call syntax, `object.?foo', method #foo is called on
|
||||||
|
`object' if it is not nil.
|
||||||
|
this is similar to `try!' in ActiveSupport, except for:
|
||||||
|
* method name is syntactically required
|
||||||
|
obj.try! {} # valid
|
||||||
|
obj.? {} # syntax error
|
||||||
|
* attribute assignment is valid
|
||||||
|
obj.?attr += 1
|
||||||
|
|
||||||
=== Core classes updates (outstanding ones only)
|
=== Core classes updates (outstanding ones only)
|
||||||
|
|
||||||
* ARGF
|
* ARGF
|
||||||
|
53
compile.c
53
compile.c
@ -1942,6 +1942,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (iobj->insn_id == BIN(branchif) ||
|
if (iobj->insn_id == BIN(branchif) ||
|
||||||
|
iobj->insn_id == BIN(branchnil) ||
|
||||||
iobj->insn_id == BIN(branchunless)) {
|
iobj->insn_id == BIN(branchunless)) {
|
||||||
/*
|
/*
|
||||||
* if L1
|
* if L1
|
||||||
@ -1955,6 +1956,31 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
|
|||||||
if (nobj->insn_id == BIN(jump)) {
|
if (nobj->insn_id == BIN(jump)) {
|
||||||
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
|
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nobj->insn_id == BIN(dup)) {
|
||||||
|
/*
|
||||||
|
* dup
|
||||||
|
* if L1
|
||||||
|
* ...
|
||||||
|
* L1:
|
||||||
|
* dup
|
||||||
|
* if L2
|
||||||
|
* =>
|
||||||
|
* dup
|
||||||
|
* if L2
|
||||||
|
* ...
|
||||||
|
* L1:
|
||||||
|
* dup
|
||||||
|
* if L2
|
||||||
|
*/
|
||||||
|
INSN *pobj = (INSN *)iobj->link.prev;
|
||||||
|
nobj = (INSN *)nobj->link.next;
|
||||||
|
/* basic blocks, with no labels in the middle */
|
||||||
|
if ((pobj && pobj->insn_id == BIN(dup)) &&
|
||||||
|
(nobj && nobj->insn_id == iobj->insn_id)) {
|
||||||
|
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_tailcallopt && iobj->insn_id == BIN(leave)) {
|
if (do_tailcallopt && iobj->insn_id == BIN(leave)) {
|
||||||
@ -4319,6 +4345,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
VALUE asgnflag;
|
VALUE asgnflag;
|
||||||
LABEL *lfin = NEW_LABEL(line);
|
LABEL *lfin = NEW_LABEL(line);
|
||||||
LABEL *lcfin = NEW_LABEL(line);
|
LABEL *lcfin = NEW_LABEL(line);
|
||||||
|
LABEL *lskip = 0;
|
||||||
/*
|
/*
|
||||||
class C; attr_accessor :c; end
|
class C; attr_accessor :c; end
|
||||||
r = C.new
|
r = C.new
|
||||||
@ -4362,6 +4389,11 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node);
|
asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node);
|
||||||
|
if (node->nd_next->nd_aid) {
|
||||||
|
lskip = NEW_LABEL(line);
|
||||||
|
ADD_INSN(ret, line, dup);
|
||||||
|
ADD_INSNL(ret, line, branchnil, lskip);
|
||||||
|
}
|
||||||
ADD_INSN(ret, line, dup);
|
ADD_INSN(ret, line, dup);
|
||||||
ADD_SEND(ret, line, vid, INT2FIX(0));
|
ADD_SEND(ret, line, vid, INT2FIX(0));
|
||||||
|
|
||||||
@ -4385,6 +4417,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
|
|
||||||
ADD_LABEL(ret, lfin);
|
ADD_LABEL(ret, lfin);
|
||||||
ADD_INSN(ret, line, pop);
|
ADD_INSN(ret, line, pop);
|
||||||
|
if (lskip) {
|
||||||
|
ADD_LABEL(ret, lskip);
|
||||||
|
}
|
||||||
if (poped) {
|
if (poped) {
|
||||||
/* we can apply more optimize */
|
/* we can apply more optimize */
|
||||||
ADD_INSN(ret, line, pop);
|
ADD_INSN(ret, line, pop);
|
||||||
@ -4392,14 +4427,16 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
|
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
|
||||||
ADD_SEND(ret, line, node->nd_next->nd_mid,
|
ADD_SEND(ret, line, atype, INT2FIX(1));
|
||||||
INT2FIX(1));
|
|
||||||
if (!poped) {
|
if (!poped) {
|
||||||
ADD_INSN(ret, line, swap);
|
ADD_INSN(ret, line, swap);
|
||||||
ADD_INSN1(ret, line, topn, INT2FIX(1));
|
ADD_INSN1(ret, line, topn, INT2FIX(1));
|
||||||
}
|
}
|
||||||
ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag));
|
ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag));
|
||||||
ADD_INSN(ret, line, pop);
|
ADD_INSN(ret, line, pop);
|
||||||
|
if (lskip) {
|
||||||
|
ADD_LABEL(ret, lskip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4548,6 +4585,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case NODE_QCALL:
|
||||||
case NODE_FCALL:
|
case NODE_FCALL:
|
||||||
case NODE_VCALL:{ /* VCALL: variable or call */
|
case NODE_VCALL:{ /* VCALL: variable or call */
|
||||||
/*
|
/*
|
||||||
@ -4557,6 +4595,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
*/
|
*/
|
||||||
DECL_ANCHOR(recv);
|
DECL_ANCHOR(recv);
|
||||||
DECL_ANCHOR(args);
|
DECL_ANCHOR(args);
|
||||||
|
LABEL *lskip = 0;
|
||||||
ID mid = node->nd_mid;
|
ID mid = node->nd_mid;
|
||||||
VALUE argc;
|
VALUE argc;
|
||||||
unsigned int flag = 0;
|
unsigned int flag = 0;
|
||||||
@ -4631,8 +4670,13 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
/* receiver */
|
/* receiver */
|
||||||
if (type == NODE_CALL) {
|
if (type == NODE_CALL || type == NODE_QCALL) {
|
||||||
COMPILE(recv, "recv", node->nd_recv);
|
COMPILE(recv, "recv", node->nd_recv);
|
||||||
|
if (type == NODE_QCALL) {
|
||||||
|
lskip = NEW_LABEL(line);
|
||||||
|
ADD_INSN(recv, line, dup);
|
||||||
|
ADD_INSNL(recv, line, branchnil, lskip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (type == NODE_FCALL || type == NODE_VCALL) {
|
else if (type == NODE_FCALL || type == NODE_VCALL) {
|
||||||
ADD_CALL_RECEIVER(recv, line);
|
ADD_CALL_RECEIVER(recv, line);
|
||||||
@ -4662,6 +4706,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||||||
|
|
||||||
ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords);
|
ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords);
|
||||||
|
|
||||||
|
if (lskip) {
|
||||||
|
ADD_LABEL(ret, lskip);
|
||||||
|
}
|
||||||
if (poped) {
|
if (poped) {
|
||||||
ADD_INSN(ret, line, pop);
|
ADD_INSN(ret, line, pop);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,10 @@ This sends the +my_method+ message to +my_object+. Any object can be a
|
|||||||
receiver but depending on the method's visibility sending a message may raise a
|
receiver but depending on the method's visibility sending a message may raise a
|
||||||
NoMethodError.
|
NoMethodError.
|
||||||
|
|
||||||
|
You may use <code>.?</code> to designate a receiver, then +my_method+ is not
|
||||||
|
invoked and the result is +nil+ when the receiver is +nil+. In that case, the
|
||||||
|
argument of +my_method+ are not evaluated.
|
||||||
|
|
||||||
You may also use <code>::</code> to designate a receiver, but this is rarely
|
You may also use <code>::</code> to designate a receiver, but this is rarely
|
||||||
used due to the potential for confusion with <code>::</code> for namespaces.
|
used due to the potential for confusion with <code>::</code> for namespaces.
|
||||||
|
|
||||||
|
17
insns.def
17
insns.def
@ -1132,6 +1132,23 @@ branchunless
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@c jump
|
||||||
|
@e if val is nil, set PC to (PC + dst).
|
||||||
|
@j もし val が nil ならば、PC を (PC + dst) にする。
|
||||||
|
*/
|
||||||
|
DEFINE_INSN
|
||||||
|
branchnil
|
||||||
|
(OFFSET dst)
|
||||||
|
(VALUE val)
|
||||||
|
()
|
||||||
|
{
|
||||||
|
if (NIL_P(val)) {
|
||||||
|
RUBY_VM_CHECK_INTS(th);
|
||||||
|
JUMP(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************/
|
/**********************************************************/
|
||||||
/* for optimize */
|
/* for optimize */
|
||||||
|
5
node.c
5
node.c
@ -357,7 +357,10 @@ dump_node(VALUE buf, VALUE indent, int comment, NODE *node)
|
|||||||
ANN(" where [attr]: [nd_next->nd_vid]");
|
ANN(" where [attr]: [nd_next->nd_vid]");
|
||||||
ANN("example: struct.field += foo");
|
ANN("example: struct.field += foo");
|
||||||
F_NODE(nd_recv, "receiver");
|
F_NODE(nd_recv, "receiver");
|
||||||
F_ID(nd_next->nd_vid, "attr");
|
F_CUSTOM1(nd_next->nd_vid, "attr") {
|
||||||
|
if (node->nd_next->nd_aid) A("? ");
|
||||||
|
A_ID(node->nd_next->nd_vid);
|
||||||
|
}
|
||||||
F_CUSTOM1(nd_next->nd_mid, "operator") {
|
F_CUSTOM1(nd_next->nd_mid, "operator") {
|
||||||
switch (node->nd_next->nd_mid) {
|
switch (node->nd_next->nd_mid) {
|
||||||
case 0: A("0 (||)"); break;
|
case 0: A("0 (||)"); break;
|
||||||
|
6
node.h
6
node.h
@ -96,6 +96,8 @@ enum node_type {
|
|||||||
#define NODE_FCALL NODE_FCALL
|
#define NODE_FCALL NODE_FCALL
|
||||||
NODE_VCALL,
|
NODE_VCALL,
|
||||||
#define NODE_VCALL NODE_VCALL
|
#define NODE_VCALL NODE_VCALL
|
||||||
|
NODE_QCALL,
|
||||||
|
#define NODE_QCALL NODE_QCALL
|
||||||
NODE_SUPER,
|
NODE_SUPER,
|
||||||
#define NODE_SUPER NODE_SUPER
|
#define NODE_SUPER NODE_SUPER
|
||||||
NODE_ZSUPER,
|
NODE_ZSUPER,
|
||||||
@ -394,8 +396,8 @@ typedef struct RNode {
|
|||||||
#define NEW_CVASGN(v,val) NEW_NODE(NODE_CVASGN,v,val,0)
|
#define NEW_CVASGN(v,val) NEW_NODE(NODE_CVASGN,v,val,0)
|
||||||
#define NEW_CVDECL(v,val) NEW_NODE(NODE_CVDECL,v,val,0)
|
#define NEW_CVDECL(v,val) NEW_NODE(NODE_CVDECL,v,val,0)
|
||||||
#define NEW_OP_ASGN1(p,id,a) NEW_NODE(NODE_OP_ASGN1,p,id,a)
|
#define NEW_OP_ASGN1(p,id,a) NEW_NODE(NODE_OP_ASGN1,p,id,a)
|
||||||
#define NEW_OP_ASGN2(r,i,o,val) NEW_NODE(NODE_OP_ASGN2,r,val,NEW_OP_ASGN22(i,o))
|
#define NEW_OP_ASGN2(r,t,i,o,val) NEW_NODE(NODE_OP_ASGN2,r,val,NEW_OP_ASGN22(i,o,t))
|
||||||
#define NEW_OP_ASGN22(i,o) NEW_NODE(NODE_OP_ASGN2,i,o,0)
|
#define NEW_OP_ASGN22(i,o,t) NEW_NODE(NODE_OP_ASGN2,i,o,t)
|
||||||
#define NEW_OP_ASGN_OR(i,val) NEW_NODE(NODE_OP_ASGN_OR,i,val,0)
|
#define NEW_OP_ASGN_OR(i,val) NEW_NODE(NODE_OP_ASGN_OR,i,val,0)
|
||||||
#define NEW_OP_ASGN_AND(i,val) NEW_NODE(NODE_OP_ASGN_AND,i,val,0)
|
#define NEW_OP_ASGN_AND(i,val) NEW_NODE(NODE_OP_ASGN_AND,i,val,0)
|
||||||
#define NEW_OP_CDECL(v,op,val) NEW_NODE(NODE_OP_CDECL,v,val,op)
|
#define NEW_OP_CDECL(v,op,val) NEW_NODE(NODE_OP_CDECL,v,val,op)
|
||||||
|
81
parse.y
81
parse.y
@ -371,6 +371,9 @@ static int parser_yyerror(struct parser_params*, const char*);
|
|||||||
#define ruby_coverage (parser->coverage)
|
#define ruby_coverage (parser->coverage)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define NODE_CALL_Q(q) (((q) == tDOTQ) ? NODE_QCALL : NODE_CALL)
|
||||||
|
#define NEW_QCALL(q,r,m,a) NEW_NODE(NODE_CALL_Q(q),r,m,a)
|
||||||
|
|
||||||
static int yylex(YYSTYPE*, struct parser_params*);
|
static int yylex(YYSTYPE*, struct parser_params*);
|
||||||
|
|
||||||
#ifndef RIPPER
|
#ifndef RIPPER
|
||||||
@ -457,8 +460,8 @@ static NODE *node_assign_gen(struct parser_params*,NODE*,NODE*);
|
|||||||
#define node_assign(node1, node2) node_assign_gen(parser, (node1), (node2))
|
#define node_assign(node1, node2) node_assign_gen(parser, (node1), (node2))
|
||||||
|
|
||||||
static NODE *new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
|
static NODE *new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
|
||||||
static NODE *new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op, NODE *rhs);
|
static NODE *new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID atype, ID attr, ID op, NODE *rhs);
|
||||||
#define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (attr), (op), (rhs))
|
#define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (type), (attr), (op), (rhs))
|
||||||
static NODE *new_const_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
|
static NODE *new_const_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
|
||||||
#define new_const_op_assign(lhs, op, rhs) new_const_op_assign_gen(parser, (lhs), (op), (rhs))
|
#define new_const_op_assign(lhs, op, rhs) new_const_op_assign_gen(parser, (lhs), (op), (rhs))
|
||||||
|
|
||||||
@ -844,7 +847,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
|
|||||||
%type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner
|
%type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner
|
||||||
%type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3
|
%type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3
|
||||||
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg
|
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg
|
||||||
%type <id> f_kwrest f_label f_arg_asgn
|
%type <id> f_kwrest f_label f_arg_asgn call_op
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
/*%
|
/*%
|
||||||
%type <val> program reswords then do dot_or_colon
|
%type <val> program reswords then do dot_or_colon
|
||||||
@ -869,6 +872,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
|
|||||||
%token tASET RUBY_TOKEN(ASET) "[]="
|
%token tASET RUBY_TOKEN(ASET) "[]="
|
||||||
%token tLSHFT RUBY_TOKEN(LSHFT) "<<"
|
%token tLSHFT RUBY_TOKEN(LSHFT) "<<"
|
||||||
%token tRSHFT RUBY_TOKEN(RSHFT) ">>"
|
%token tRSHFT RUBY_TOKEN(RSHFT) ">>"
|
||||||
|
%token tDOTQ RUBY_TOKEN(DOTQ) ".?"
|
||||||
%token tCOLON2 "::"
|
%token tCOLON2 "::"
|
||||||
%token tCOLON3 ":: at EXPR_BEG"
|
%token tCOLON3 ":: at EXPR_BEG"
|
||||||
%token <id> tOP_ASGN /* +=, -= etc. */
|
%token <id> tOP_ASGN /* +=, -= etc. */
|
||||||
@ -1260,15 +1264,15 @@ stmt : keyword_alias fitem {lex_state = EXPR_FNAME;} fitem
|
|||||||
$$ = dispatch3(opassign, $$, $5, $6);
|
$$ = dispatch3(opassign, $$, $5, $6);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' tIDENTIFIER tOP_ASGN command_call
|
| primary_value call_op tIDENTIFIER tOP_ASGN command_call
|
||||||
{
|
{
|
||||||
value_expr($5);
|
value_expr($5);
|
||||||
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
|
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
|
||||||
}
|
}
|
||||||
| primary_value '.' tCONSTANT tOP_ASGN command_call
|
| primary_value call_op tCONSTANT tOP_ASGN command_call
|
||||||
{
|
{
|
||||||
value_expr($5);
|
value_expr($5);
|
||||||
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
|
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
|
||||||
}
|
}
|
||||||
| primary_value tCOLON2 tCONSTANT tOP_ASGN command_call
|
| primary_value tCOLON2 tCONSTANT tOP_ASGN command_call
|
||||||
{
|
{
|
||||||
@ -1456,24 +1460,24 @@ command : fcall command_args %prec tLOWEST
|
|||||||
$$ = method_add_block($$, $3);
|
$$ = method_add_block($$, $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' operation2 command_args %prec tLOWEST
|
| primary_value call_op operation2 command_args %prec tLOWEST
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = NEW_CALL($1, $3, $4);
|
$$ = NEW_QCALL($2, $1, $3, $4);
|
||||||
fixpos($$, $1);
|
fixpos($$, $1);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4);
|
$$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' operation2 command_args cmd_brace_block
|
| primary_value call_op operation2 command_args cmd_brace_block
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
block_dup_check($4,$5);
|
block_dup_check($4,$5);
|
||||||
$5->nd_iter = NEW_CALL($1, $3, $4);
|
$5->nd_iter = NEW_QCALL($2, $1, $3, $4);
|
||||||
$$ = $5;
|
$$ = $5;
|
||||||
fixpos($$, $1);
|
fixpos($$, $1);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4);
|
$$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
|
||||||
$$ = method_add_block($$, $5);
|
$$ = method_add_block($$, $5);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
@ -1713,12 +1717,12 @@ mlhs_node : user_variable
|
|||||||
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
|
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' tIDENTIFIER
|
| primary_value call_op tIDENTIFIER
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = attrset($1, $3);
|
$$ = attrset($1, $3);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value tCOLON2 tIDENTIFIER
|
| primary_value tCOLON2 tIDENTIFIER
|
||||||
@ -1729,12 +1733,12 @@ mlhs_node : user_variable
|
|||||||
$$ = dispatch2(const_path_field, $1, $3);
|
$$ = dispatch2(const_path_field, $1, $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' tCONSTANT
|
| primary_value call_op tCONSTANT
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = attrset($1, $3);
|
$$ = attrset($1, $3);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value tCOLON2 tCONSTANT
|
| primary_value tCOLON2 tCONSTANT
|
||||||
@ -1804,12 +1808,12 @@ lhs : user_variable
|
|||||||
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
|
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' tIDENTIFIER
|
| primary_value call_op tIDENTIFIER
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = attrset($1, $3);
|
$$ = attrset($1, $3);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value tCOLON2 tIDENTIFIER
|
| primary_value tCOLON2 tIDENTIFIER
|
||||||
@ -1820,12 +1824,12 @@ lhs : user_variable
|
|||||||
$$ = dispatch3(field, $1, ID2SYM(idCOLON2), $3);
|
$$ = dispatch3(field, $1, ID2SYM(idCOLON2), $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' tCONSTANT
|
| primary_value call_op tCONSTANT
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = attrset($1, $3);
|
$$ = attrset($1, $3);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value tCOLON2 tCONSTANT
|
| primary_value tCOLON2 tCONSTANT
|
||||||
@ -2064,15 +2068,15 @@ arg : lhs '=' arg
|
|||||||
$$ = dispatch3(opassign, $1, $5, $6);
|
$$ = dispatch3(opassign, $1, $5, $6);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' tIDENTIFIER tOP_ASGN arg
|
| primary_value call_op tIDENTIFIER tOP_ASGN arg
|
||||||
{
|
{
|
||||||
value_expr($5);
|
value_expr($5);
|
||||||
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
|
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
|
||||||
}
|
}
|
||||||
| primary_value '.' tCONSTANT tOP_ASGN arg
|
| primary_value call_op tCONSTANT tOP_ASGN arg
|
||||||
{
|
{
|
||||||
value_expr($5);
|
value_expr($5);
|
||||||
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
|
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
|
||||||
}
|
}
|
||||||
| primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg
|
| primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg
|
||||||
{
|
{
|
||||||
@ -3648,7 +3652,7 @@ method_call : fcall paren_args
|
|||||||
$$ = method_arg(dispatch1(fcall, $1), $2);
|
$$ = method_arg(dispatch1(fcall, $1), $2);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.' operation2
|
| primary_value call_op operation2
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$<num>$ = ruby_sourceline;
|
$<num>$ = ruby_sourceline;
|
||||||
@ -3657,10 +3661,10 @@ method_call : fcall paren_args
|
|||||||
opt_paren_args
|
opt_paren_args
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = NEW_CALL($1, $3, $5);
|
$$ = NEW_QCALL($2, $1, $3, $5);
|
||||||
nd_set_line($$, $<num>4);
|
nd_set_line($$, $<num>4);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch3(call, $1, ripper_id2sym('.'), $3);
|
$$ = dispatch3(call, $1, ripper_id2sym($2), $3);
|
||||||
$$ = method_optarg($$, $5);
|
$$ = method_optarg($$, $5);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
@ -3688,7 +3692,7 @@ method_call : fcall paren_args
|
|||||||
$$ = dispatch3(call, $1, ID2SYM(idCOLON2), $3);
|
$$ = dispatch3(call, $1, ID2SYM(idCOLON2), $3);
|
||||||
%*/
|
%*/
|
||||||
}
|
}
|
||||||
| primary_value '.'
|
| primary_value call_op
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$<num>$ = ruby_sourceline;
|
$<num>$ = ruby_sourceline;
|
||||||
@ -3697,10 +3701,10 @@ method_call : fcall paren_args
|
|||||||
paren_args
|
paren_args
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = NEW_CALL($1, idCall, $4);
|
$$ = NEW_QCALL($2, $1, idCall, $4);
|
||||||
nd_set_line($$, $<num>3);
|
nd_set_line($$, $<num>3);
|
||||||
/*%
|
/*%
|
||||||
$$ = dispatch3(call, $1, ripper_id2sym('.'),
|
$$ = dispatch3(call, $1, ripper_id2sym($2),
|
||||||
ID2SYM(idCall));
|
ID2SYM(idCall));
|
||||||
$$ = method_optarg($$, $4);
|
$$ = method_optarg($$, $4);
|
||||||
%*/
|
%*/
|
||||||
@ -5103,7 +5107,7 @@ operation3 : tIDENTIFIER
|
|||||||
| op
|
| op
|
||||||
;
|
;
|
||||||
|
|
||||||
dot_or_colon : '.'
|
dot_or_colon : call_op
|
||||||
/*%c%*/
|
/*%c%*/
|
||||||
/*%c
|
/*%c
|
||||||
{ $$ = $<val>1; }
|
{ $$ = $<val>1; }
|
||||||
@ -5115,6 +5119,10 @@ dot_or_colon : '.'
|
|||||||
%*/
|
%*/
|
||||||
;
|
;
|
||||||
|
|
||||||
|
call_op : '.' {$$ = '.';}
|
||||||
|
| tDOTQ {$$ = tDOTQ;}
|
||||||
|
;
|
||||||
|
|
||||||
opt_terms : /* none */
|
opt_terms : /* none */
|
||||||
| terms
|
| terms
|
||||||
;
|
;
|
||||||
@ -8356,6 +8364,10 @@ parser_yylex(struct parser_params *parser)
|
|||||||
pushback(c);
|
pushback(c);
|
||||||
return tDOT2;
|
return tDOT2;
|
||||||
}
|
}
|
||||||
|
if (c == '?') {
|
||||||
|
lex_state = EXPR_DOT;
|
||||||
|
return tDOTQ;
|
||||||
|
}
|
||||||
pushback(c);
|
pushback(c);
|
||||||
if (c != -1 && ISDIGIT(c)) {
|
if (c != -1 && ISDIGIT(c)) {
|
||||||
yyerror("no .<digit> floating literal anymore; put 0 before dot");
|
yyerror("no .<digit> floating literal anymore; put 0 before dot");
|
||||||
@ -10036,7 +10048,8 @@ new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static NODE *
|
static NODE *
|
||||||
new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op, NODE *rhs)
|
new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs,
|
||||||
|
ID atype, ID attr, ID op, NODE *rhs)
|
||||||
{
|
{
|
||||||
NODE *asgn;
|
NODE *asgn;
|
||||||
|
|
||||||
@ -10046,7 +10059,7 @@ new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op,
|
|||||||
else if (op == tANDOP) {
|
else if (op == tANDOP) {
|
||||||
op = 1;
|
op = 1;
|
||||||
}
|
}
|
||||||
asgn = NEW_OP_ASGN2(lhs, attr, op, rhs);
|
asgn = NEW_OP_ASGN2(lhs, (atype == tDOTQ), attr, op, rhs);
|
||||||
fixpos(asgn, lhs);
|
fixpos(asgn, lhs);
|
||||||
return asgn;
|
return asgn;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ op_id_offset = 128
|
|||||||
token_op_ids = %w[
|
token_op_ids = %w[
|
||||||
tDOT2 tDOT3 tUPLUS tUMINUS tPOW tDSTAR tCMP tLSHFT tRSHFT
|
tDOT2 tDOT3 tUPLUS tUMINUS tPOW tDSTAR tCMP tLSHFT tRSHFT
|
||||||
tLEQ tGEQ tEQ tEQQ tNEQ tMATCH tNMATCH tAREF tASET
|
tLEQ tGEQ tEQ tEQQ tNEQ tMATCH tNMATCH tAREF tASET
|
||||||
tCOLON2 tCOLON3 tANDOP tOROP
|
tCOLON2 tCOLON3 tANDOP tOROP tDOTQ
|
||||||
]
|
]
|
||||||
|
|
||||||
defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/id.def")
|
defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/id.def")
|
||||||
|
@ -31,4 +31,16 @@ class TestCall < Test::Unit::TestCase
|
|||||||
assert_nothing_raised(ArgumentError) {o.foo}
|
assert_nothing_raised(ArgumentError) {o.foo}
|
||||||
assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)}
|
assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_safe_call
|
||||||
|
s = Struct.new(:x, :y)
|
||||||
|
o = s.new("x")
|
||||||
|
assert_equal("X", o.x.?upcase)
|
||||||
|
assert_nil(o.y.?upcase)
|
||||||
|
assert_equal("x", o.x)
|
||||||
|
o.?x = 6
|
||||||
|
assert_equal(6, o.x)
|
||||||
|
o.?x *= 7
|
||||||
|
assert_equal(42, o.x)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user