git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@67278 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2019-03-17 05:21:18 +00:00
Родитель 0fa4a6a618
Коммит 12acc751e3
8 изменённых файлов: 159 добавлений и 12 удалений

3
NEWS
Просмотреть файл

@ -25,6 +25,9 @@ sufficient information, see the ChangeLog file or Redmine
* Non-Symbol keys in a keyword arguments hash was prohibited at 2.6.0, but * Non-Symbol keys in a keyword arguments hash was prohibited at 2.6.0, but
now allowed again. [Bug #15658] now allowed again. [Bug #15658]
* Numbered parameter as the default block parameter is introduced as an
experimental feature. [Feature #4475]
=== Core classes updates (outstanding ones only) === Core classes updates (outstanding ones only)
Enumerable:: Enumerable::

Просмотреть файл

@ -532,7 +532,7 @@ end
assert_syntax_error "unterminated string meets end of file", '().."', '[ruby-dev:29732]' assert_syntax_error "unterminated string meets end of file", '().."', '[ruby-dev:29732]'
assert_equal %q{[]}, %q{$&;[]}, '[ruby-dev:31068]' assert_equal %q{[]}, %q{$&;[]}, '[ruby-dev:31068]'
assert_syntax_error "syntax error, unexpected *, expecting '}'", %q{{*0}}, '[ruby-dev:31072]' assert_syntax_error "syntax error, unexpected *, expecting '}'", %q{{*0}}, '[ruby-dev:31072]'
assert_syntax_error "`@0' is not allowed as an instance variable name", %q{@0..0}, '[ruby-dev:31095]' assert_syntax_error "leading zero is not allowed as a numbered parameter", %q{@0..0}, '[ruby-dev:31095]'
assert_syntax_error "identifier $00 is not valid to get", %q{$00..0}, '[ruby-dev:31100]' assert_syntax_error "identifier $00 is not valid to get", %q{$00..0}, '[ruby-dev:31100]'
assert_syntax_error "identifier $00 is not valid to set", %q{0..$00=1} assert_syntax_error "identifier $00 is not valid to set", %q{0..$00=1}
assert_equal %q{0}, %q{[*0];0}, '[ruby-dev:31102]' assert_equal %q{0}, %q{[*0];0}, '[ruby-dev:31102]'

Просмотреть файл

@ -51,6 +51,7 @@ typedef struct {
ID ripper_id_label_end; ID ripper_id_label_end;
ID ripper_id_tlambda; ID ripper_id_tlambda;
ID ripper_id_tlambeg; ID ripper_id_tlambeg;
ID ripper_id_tnumparam;
ID ripper_id_ignored_nl; ID ripper_id_ignored_nl;
ID ripper_id_comment; ID ripper_id_comment;
@ -113,6 +114,7 @@ ripper_init_eventids2(void)
set_id2(label_end); set_id2(label_end);
set_id2(tlambda); set_id2(tlambda);
set_id2(tlambeg); set_id2(tlambeg);
set_id2(tnumparam);
set_id2(ignored_nl); set_id2(ignored_nl);
set_id2(comment); set_id2(comment);
@ -276,6 +278,7 @@ static const struct token_assoc {
{tLABEL_END, O(label_end)}, {tLABEL_END, O(label_end)},
{tLAMBDA, O(tlambda)}, {tLAMBDA, O(tlambda)},
{tLAMBEG, O(tlambeg)}, {tLAMBEG, O(tlambeg)},
{tNUMPARAM, O(tnumparam)},
/* ripper specific tokens */ /* ripper specific tokens */
{tIGNORED_NL, O(ignored_nl)}, {tIGNORED_NL, O(ignored_nl)},

121
parse.y
Просмотреть файл

@ -167,6 +167,8 @@ struct local_vars {
struct local_vars *prev; struct local_vars *prev;
}; };
#define NUMPARAM_MAX 100 /* INT_MAX */
#define DVARS_INHERIT ((void*)1) #define DVARS_INHERIT ((void*)1)
#define DVARS_TOPSCOPE NULL #define DVARS_TOPSCOPE NULL
#define DVARS_TERMINAL_P(tbl) ((tbl) == DVARS_INHERIT || (tbl) == DVARS_TOPSCOPE) #define DVARS_TERMINAL_P(tbl) ((tbl) == DVARS_INHERIT || (tbl) == DVARS_TOPSCOPE)
@ -244,6 +246,8 @@ struct parser_params {
rb_ast_t *ast; rb_ast_t *ast;
int node_id; int node_id;
int max_numparam;
unsigned int command_start:1; unsigned int command_start:1;
unsigned int eofp: 1; unsigned int eofp: 1;
unsigned int ruby__end__seen: 1; unsigned int ruby__end__seen: 1;
@ -413,6 +417,8 @@ static NODE *method_add_block(struct parser_params*p, NODE *m, NODE *b, const YY
static NODE *new_args(struct parser_params*,NODE*,NODE*,ID,NODE*,NODE*,const YYLTYPE*); static NODE *new_args(struct parser_params*,NODE*,NODE*,ID,NODE*,NODE*,const YYLTYPE*);
static NODE *new_args_tail(struct parser_params*,NODE*,ID,ID,const YYLTYPE*); static NODE *new_args_tail(struct parser_params*,NODE*,ID,ID,const YYLTYPE*);
static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc); static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc);
static NODE *args_with_numbered(struct parser_params*,NODE*,int);
static ID numparam_id(struct parser_params*,int);
static VALUE negate_lit(struct parser_params*, VALUE); static VALUE negate_lit(struct parser_params*, VALUE);
static NODE *ret_args(struct parser_params*,NODE*); static NODE *ret_args(struct parser_params*,NODE*);
@ -686,6 +692,12 @@ new_args_tail(struct parser_params *p, VALUE kw_args, VALUE kw_rest_arg, VALUE b
return (VALUE)t; return (VALUE)t;
} }
static inline VALUE
args_with_numbered(struct parser_params *p, VALUE args, int max_numparam)
{
return args;
}
#define new_defined(p,expr,loc) dispatch1(defined, (expr)) #define new_defined(p,expr,loc) dispatch1(defined, (expr))
static VALUE heredoc_dedent(struct parser_params*,VALUE); static VALUE heredoc_dedent(struct parser_params*,VALUE);
@ -854,6 +866,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%token <node> tBACK_REF "back reference" %token <node> tBACK_REF "back reference"
%token <node> tSTRING_CONTENT "literal content" %token <node> tSTRING_CONTENT "literal content"
%token <num> tREGEXP_END %token <num> tREGEXP_END
%token <num> tNUMPARAM "numbered parameter"
%type <node> singleton strings string string1 xstring regexp %type <node> singleton strings string string1 xstring regexp
%type <node> string_contents xstring_contents regexp_contents string_content %type <node> string_contents xstring_contents regexp_contents string_content
@ -3118,6 +3131,7 @@ block_param_def : '|' opt_bv_decl '|'
} }
| tOROP | tOROP
{ {
p->max_numparam = -1;
/*%%%*/ /*%%%*/
$$ = 0; $$ = 0;
/*% %*/ /*% %*/
@ -3126,6 +3140,7 @@ block_param_def : '|' opt_bv_decl '|'
| '|' block_param opt_bv_decl '|' | '|' block_param opt_bv_decl '|'
{ {
p->cur_arg = 0; p->cur_arg = 0;
p->max_numparam = -1;
/*%%%*/ /*%%%*/
$$ = $2; $$ = $2;
/*% %*/ /*% %*/
@ -3357,20 +3372,34 @@ brace_block : '{' brace_body '}'
; ;
brace_body : {$<vars>$ = dyna_push(p);} brace_body : {$<vars>$ = dyna_push(p);}
{
$<num>$ = p->max_numparam;
p->max_numparam = 0;
}
opt_block_param compstmt opt_block_param compstmt
{ {
int max_numparam = p->max_numparam;
p->max_numparam = $<num>2;
$3 = args_with_numbered(p, $3, max_numparam);
/*%%%*/ /*%%%*/
$$ = NEW_ITER($2, $3, &@$); $$ = NEW_ITER($3, $4, &@$);
/*% %*/ /*% %*/
/*% ripper: brace_block!(escape_Qundef($2), $3) %*/ /*% ripper: brace_block!(escape_Qundef($3), $4) %*/
dyna_pop(p, $<vars>1); dyna_pop(p, $<vars>1);
} }
; ;
do_body : {$<vars>$ = dyna_push(p);} do_body : {$<vars>$ = dyna_push(p);}
{CMDARG_PUSH(0);} {
$<num>$ = p->max_numparam;
p->max_numparam = 0;
CMDARG_PUSH(0);
}
opt_block_param bodystmt opt_block_param bodystmt
{ {
int max_numparam = p->max_numparam;
p->max_numparam = $<num>2;
$3 = args_with_numbered(p, $3, max_numparam);
/*%%%*/ /*%%%*/
$$ = NEW_ITER($3, $4, &@$); $$ = NEW_ITER($3, $4, &@$);
/*% %*/ /*% %*/
@ -3773,6 +3802,13 @@ string_dvar : tGVAR
/*% %*/ /*% %*/
/*% ripper: var_ref!($1) %*/ /*% ripper: var_ref!($1) %*/
} }
| tNUMPARAM
{
/*%%%*/
$$ = NEW_DVAR(numparam_id(p, $1), &@1);
/*% %*/
/*% ripper: var_ref!(number_arg!($1)) %*/
}
| backref | backref
; ;
@ -3828,6 +3864,13 @@ user_variable : tIDENTIFIER
| tGVAR | tGVAR
| tCONSTANT | tCONSTANT
| tCVAR | tCVAR
| tNUMPARAM
{
/*%%%*/
$$ = numparam_id(p, $1);
/*% %*/
/*% ripper: number_arg!($1) %*/
}
; ;
keyword_variable: keyword_nil {$$ = KWD2EID(nil, $1);} keyword_variable: keyword_nil {$$ = KWD2EID(nil, $1);}
@ -5967,6 +6010,9 @@ parser_peek_variable_name(struct parser_params *p)
if (++ptr >= p->lex.pend) return 0; if (++ptr >= p->lex.pend) return 0;
c = *ptr; c = *ptr;
} }
else if (ISDIGIT(c)) {
return tSTRING_DVAR;
}
break; break;
case '{': case '{':
p->lex.pcur = ptr; p->lex.pcur = ptr;
@ -7612,12 +7658,34 @@ parse_atmark(struct parser_params *p, const enum lex_state_e last_state)
return 0; return 0;
} }
else if (ISDIGIT(c)) { else if (ISDIGIT(c)) {
RUBY_SET_YYLLOC(loc);
pushback(p, c);
if (result == tIVAR) { if (result == tIVAR) {
compile_error(p, "`@%c' is not allowed as an instance variable name", c); const char *ptr = p->lex.pcur - 1;
size_t len = p->lex.pend - ptr;
int overflow;
unsigned long n = ruby_scan_digits(ptr, len, 10, &len, &overflow);
p->lex.pcur = ptr + len;
RUBY_SET_YYLLOC(loc);
if (ptr[0] == '0') {
compile_error(p, "leading zero is not allowed as a numbered parameter");
}
else if (overflow || n > NUMPARAM_MAX) {
compile_error(p, "too large numbered parameter");
}
else if (DVARS_TERMINAL_P(p->lvtbl->args) || DVARS_TERMINAL_P(p->lvtbl->args->prev)) {
compile_error(p, "numbered parameter outside block");
}
else if (p->max_numparam < 0) {
compile_error(p, "ordinary parameter is defined");
}
else {
set_yylval_num((int)n);
SET_LEX_STATE(EXPR_ARG);
return tNUMPARAM;
}
} }
else { else {
RUBY_SET_YYLLOC(loc);
pushback(p, c);
compile_error(p, "`@@%c' is not allowed as a class variable name", c); compile_error(p, "`@@%c' is not allowed as a class variable name", c);
} }
parser_show_error_line(p, &loc); parser_show_error_line(p, &loc);
@ -8876,6 +8944,12 @@ gettable(struct parser_params *p, ID id, const YYLTYPE *loc)
return NEW_LIT(add_mark_object(p, rb_enc_from_encoding(p->enc)), loc); return NEW_LIT(add_mark_object(p, rb_enc_from_encoding(p->enc)), loc);
} }
switch (id_type(id)) { switch (id_type(id)) {
case ID_INTERNAL:
{
int idx = vtable_included(p->lvtbl->args, id);
if (idx) return NEW_DVAR(id, loc);
}
break;
case ID_LOCAL: case ID_LOCAL:
if (dyna_in_block(p) && dvar_defined_ref(p, id, &vidp)) { if (dyna_in_block(p) && dvar_defined_ref(p, id, &vidp)) {
if (id == p->cur_arg) { if (id == p->cur_arg) {
@ -9331,6 +9405,15 @@ assignable0(struct parser_params *p, ID id, const char **err)
*err = "dynamic constant assignment"; *err = "dynamic constant assignment";
return -1; return -1;
case ID_CLASS: return NODE_CVASGN; case ID_CLASS: return NODE_CVASGN;
case ID_INTERNAL:
{
int idx = vtable_included(p->lvtbl->args, id);
if (idx) {
compile_error(p, "Can't assign to numbered parameter @%d", idx);
break;
}
}
/* fallthru */
default: default:
compile_error(p, "identifier %"PRIsVALUE" is not valid to set", rb_id2str(id)); compile_error(p, "identifier %"PRIsVALUE" is not valid to set", rb_id2str(id));
} }
@ -9355,6 +9438,21 @@ assignable(struct parser_params *p, ID id, NODE *val, const YYLTYPE *loc)
if (err) yyerror1(loc, err); if (err) yyerror1(loc, err);
return NEW_BEGIN(0, loc); return NEW_BEGIN(0, loc);
} }
static ID
numparam_id(struct parser_params *p, int idx)
{
struct vtable *args;
if (idx <= 0) return (ID)0;
if (p->max_numparam < idx) {
p->max_numparam = idx;
}
args = p->lvtbl->args;
while (idx > args->pos) {
vtable_add(args, internal_id(p));
}
return args->tbl[idx-1];
}
#else #else
static VALUE static VALUE
assignable(struct parser_params *p, VALUE lhs) assignable(struct parser_params *p, VALUE lhs)
@ -10227,6 +10325,17 @@ new_args_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, ID block,
return node; return node;
} }
static NODE *
args_with_numbered(struct parser_params *p, NODE *args, int max_numparam)
{
if (max_numparam > 0) {
if (!args) args = new_args_tail(p, 0, 0, 0, 0);
args->nd_ainfo->pre_args_num = max_numparam;
args->nd_ainfo->rest_arg = excessed_comma;
}
return args;
}
static NODE* static NODE*
dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc)
{ {

Просмотреть файл

@ -812,6 +812,12 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
assert_equal true, thru_next assert_equal true, thru_next
end end
def test_number_arg
thru_number_arg = false
parse('proc {@1}', :on_number_arg) {thru_number_arg = true}
assert_equal true, thru_number_arg
end
def test_opassign def test_opassign
thru_opassign = false thru_opassign = false
tree = parse('a += b', :on_opassign) {thru_opassign = true} tree = parse('a += b', :on_opassign) {thru_opassign = true}
@ -1477,8 +1483,11 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
assert_equal("unterminated regexp meets end of file", compile_error('/')) assert_equal("unterminated regexp meets end of file", compile_error('/'))
end end
def test_invalid_numbered_parameter_name
assert_equal("leading zero is not allowed as a numbered parameter", compile_error('proc{@0}'))
end
def test_invalid_instance_variable_name def test_invalid_instance_variable_name
assert_equal("`@1' is not allowed as an instance variable name", compile_error('@1'))
assert_equal("`@' without identifiers is not allowed as an instance variable name", compile_error('@%')) assert_equal("`@' without identifiers is not allowed as an instance variable name", compile_error('@%'))
assert_equal("`@' without identifiers is not allowed as an instance variable name", compile_error('@')) assert_equal("`@' without identifiers is not allowed as an instance variable name", compile_error('@'))
end end

Просмотреть файл

@ -259,6 +259,8 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase
scan('embvar', '"#@ivar"') scan('embvar', '"#@ivar"')
assert_equal ['#'], assert_equal ['#'],
scan('embvar', '"#@@cvar"') scan('embvar', '"#@@cvar"')
assert_equal ['#'],
scan('embvar', '"#@1"')
assert_equal [], assert_equal [],
scan('embvar', '"#lvar"') scan('embvar', '"#lvar"')
assert_equal [], assert_equal [],
@ -348,6 +350,13 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase
scan('ivar', 'm(lvar, @ivar, @@cvar, $gvar)') scan('ivar', 'm(lvar, @ivar, @@cvar, $gvar)')
end end
def test_tnumparam
assert_equal [],
scan('tnumparam', '')
assert_equal ['@1'],
scan('tnumparam', 'proc {@1}')
end
def test_kw def test_kw
assert_equal [], assert_equal [],
scan('kw', '') scan('kw', '')
@ -742,6 +751,7 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase
assert_equal [" E\n\n"], assert_equal [" E\n\n"],
scan('tstring_content', "<<""'E'\n E\n\n"), scan('tstring_content', "<<""'E'\n E\n\n"),
bug10392 bug10392
scan('tstring_content', "tap{<<""EOS}\n""there\n""heredoc\#@1xxx\nEOS")
end end
def test_heredoc_end def test_heredoc_end

Просмотреть файл

@ -363,7 +363,7 @@ class TestParse < Test::Unit::TestCase
def test_dstr_disallowed_variable def test_dstr_disallowed_variable
bug8375 = '[ruby-core:54885] [Bug #8375]' bug8375 = '[ruby-core:54885] [Bug #8375]'
%w[@ @1 @. @@ @@1 @@. $ $%].each do |src| %w[@ @. @@ @@1 @@. $ $%].each do |src|
src = '#'+src+' ' src = '#'+src+' '
str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do
break eval('"'+src+'"') break eval('"'+src+'"')
@ -378,15 +378,15 @@ class TestParse < Test::Unit::TestCase
def assert_disallowed_variable(type, noname, invalid) def assert_disallowed_variable(type, noname, invalid)
noname.each do |name| noname.each do |name|
assert_syntax_error("a = #{name}", "`#{noname[0]}' without identifiers is not allowed as #{type} variable name") assert_syntax_error("proc{a = #{name} }", "`#{noname[0]}' without identifiers is not allowed as #{type} variable name")
end end
invalid.each do |name| invalid.each do |name|
assert_syntax_error("a = #{name}", "`#{name}' is not allowed as #{type} variable name") assert_syntax_error("proc {a = #{name} }", "`#{name}' is not allowed as #{type} variable name")
end end
end end
def test_disallowed_instance_variable def test_disallowed_instance_variable
assert_disallowed_variable("an instance", %w[@ @.], %w[@1]) assert_disallowed_variable("an instance", %w[@ @.], %w[])
end end
def test_disallowed_class_variable def test_disallowed_class_variable

Просмотреть файл

@ -1291,6 +1291,19 @@ eom
assert_valid_syntax('obj::foo (1) {}') assert_valid_syntax('obj::foo (1) {}')
end end
def test_numbered_parameter
assert_valid_syntax('proc {@1}')
assert_equal(3, eval('[1,2].then {@1+@2}'))
assert_equal("12", eval('[1,2].then {"#@1#@2"}'))
assert_syntax_error('proc {|| @1}', /ordinary parameter is defined/)
assert_syntax_error('proc {|x| @1}', /ordinary parameter is defined/)
assert_syntax_error('proc {@1 = nil}', /Can't assign to numbered parameter @1/)
assert_syntax_error('proc {@01}', /leading zero/)
assert_syntax_error('proc {@1_}', /unexpected/)
assert_syntax_error('proc {@9999999999999999}', /too large/)
assert_syntax_error('@1', /outside block/)
end
private private
def not_label(x) @result = x; @not_label ||= nil end def not_label(x) @result = x; @not_label ||= nil end