[前][次][番号順一覧][スレッド一覧]

ruby-changes:55071

From: nobu <ko1@a...>
Date: Sun, 17 Mar 2019 14:21:23 +0900 (JST)
Subject: [ruby-changes:55071] nobu:r67278 (trunk): Numbered parameters [Feature #4475]

nobu	2019-03-17 14:21:18 +0900 (Sun, 17 Mar 2019)

  New Revision: 67278

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=67278

  Log:
    Numbered parameters [Feature #4475]

  Modified files:
    trunk/NEWS
    trunk/bootstraptest/test_syntax.rb
    trunk/ext/ripper/eventids2.c
    trunk/parse.y
    trunk/test/ripper/test_parser_events.rb
    trunk/test/ripper/test_scanner_events.rb
    trunk/test/ruby/test_parse.rb
    trunk/test/ruby/test_syntax.rb
Index: test/ruby/test_parse.rb
===================================================================
--- test/ruby/test_parse.rb	(revision 67277)
+++ test/ruby/test_parse.rb	(revision 67278)
@@ -363,7 +363,7 @@ class TestParse < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_parse.rb#L363
 
   def test_dstr_disallowed_variable
     bug8375 = '[ruby-core:54885] [Bug #8375]'
-    %w[@ @1 @. @@ @@1 @@. $ $%].each do |src|
+    %w[@ @. @@ @@1 @@. $ $%].each do |src|
       src = '#'+src+' '
       str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do
         break eval('"'+src+'"')
@@ -378,15 +378,15 @@ class TestParse < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_parse.rb#L378
 
   def assert_disallowed_variable(type, noname, invalid)
     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
     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
 
   def test_disallowed_instance_variable
-    assert_disallowed_variable("an instance", %w[@ @.], %w[@1])
+    assert_disallowed_variable("an instance", %w[@ @.], %w[])
   end
 
   def test_disallowed_class_variable
Index: test/ruby/test_syntax.rb
===================================================================
--- test/ruby/test_syntax.rb	(revision 67277)
+++ test/ruby/test_syntax.rb	(revision 67278)
@@ -1291,6 +1291,19 @@ eom https://github.com/ruby/ruby/blob/trunk/test/ruby/test_syntax.rb#L1291
     assert_valid_syntax('obj::foo (1) {}')
   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
 
   def not_label(x) @result = x; @not_label ||= nil end
Index: test/ripper/test_scanner_events.rb
===================================================================
--- test/ripper/test_scanner_events.rb	(revision 67277)
+++ test/ripper/test_scanner_events.rb	(revision 67278)
@@ -259,6 +259,8 @@ class TestRipper::ScannerEvents < Test:: https://github.com/ruby/ruby/blob/trunk/test/ripper/test_scanner_events.rb#L259
                  scan('embvar', '"#@ivar"')
     assert_equal ['#'],
                  scan('embvar', '"#@@cvar"')
+    assert_equal ['#'],
+                 scan('embvar', '"#@1"')
     assert_equal [],
                  scan('embvar', '"#lvar"')
     assert_equal [],
@@ -348,6 +350,13 @@ class TestRipper::ScannerEvents < Test:: https://github.com/ruby/ruby/blob/trunk/test/ripper/test_scanner_events.rb#L350
                  scan('ivar', 'm(lvar, @ivar, @@cvar, $gvar)')
   end
 
+  def test_tnumparam
+    assert_equal [],
+                 scan('tnumparam', '')
+    assert_equal ['@1'],
+                 scan('tnumparam', 'proc {@1}')
+  end
+
   def test_kw
     assert_equal [],
                  scan('kw', '')
@@ -742,6 +751,7 @@ class TestRipper::ScannerEvents < Test:: https://github.com/ruby/ruby/blob/trunk/test/ripper/test_scanner_events.rb#L751
     assert_equal [" E\n\n"],
                  scan('tstring_content', "<<""'E'\n E\n\n"),
                  bug10392
+                 scan('tstring_content', "tap{<<""EOS}\n""there\n""heredoc\#@1xxx\nEOS")
   end
 
   def test_heredoc_end
Index: test/ripper/test_parser_events.rb
===================================================================
--- test/ripper/test_parser_events.rb	(revision 67277)
+++ test/ripper/test_parser_events.rb	(revision 67278)
@@ -812,6 +812,12 @@ class TestRipper::ParserEvents < Test::U https://github.com/ruby/ruby/blob/trunk/test/ripper/test_parser_events.rb#L812
     assert_equal true, thru_next
   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
     thru_opassign = false
     tree = parse('a += b', :on_opassign) {thru_opassign = true}
@@ -1477,8 +1483,11 @@ class TestRipper::ParserEvents < Test::U https://github.com/ruby/ruby/blob/trunk/test/ripper/test_parser_events.rb#L1483
     assert_equal("unterminated regexp meets end of file", compile_error('/'))
   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
-    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('@'))
   end
Index: parse.y
===================================================================
--- parse.y	(revision 67277)
+++ parse.y	(revision 67278)
@@ -167,6 +167,8 @@ struct local_vars { https://github.com/ruby/ruby/blob/trunk/parse.y#L167
     struct local_vars *prev;
 };
 
+#define NUMPARAM_MAX 100 /* INT_MAX */
+
 #define DVARS_INHERIT ((void*)1)
 #define DVARS_TOPSCOPE NULL
 #define DVARS_TERMINAL_P(tbl) ((tbl) == DVARS_INHERIT || (tbl) == DVARS_TOPSCOPE)
@@ -244,6 +246,8 @@ struct parser_params { https://github.com/ruby/ruby/blob/trunk/parse.y#L246
     rb_ast_t *ast;
     int node_id;
 
+    int max_numparam;
+
     unsigned int command_start:1;
     unsigned int eofp: 1;
     unsigned int ruby__end__seen: 1;
@@ -413,6 +417,8 @@ static NODE *method_add_block(struct par https://github.com/ruby/ruby/blob/trunk/parse.y#L417
 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_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 NODE *ret_args(struct parser_params*,NODE*);
@@ -686,6 +692,12 @@ new_args_tail(struct parser_params *p, V https://github.com/ruby/ruby/blob/trunk/parse.y#L692
     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))
 
 static VALUE heredoc_dedent(struct parser_params*,VALUE);
@@ -854,6 +866,7 @@ static void token_info_warn(struct parse https://github.com/ruby/ruby/blob/trunk/parse.y#L866
 %token <node> tBACK_REF      "back reference"
 %token <node> tSTRING_CONTENT "literal content"
 %token <num>  tREGEXP_END
+%token <num>  tNUMPARAM      "numbered parameter"
 
 %type <node> singleton strings string string1 xstring regexp
 %type <node> string_contents xstring_contents regexp_contents string_content
@@ -3118,6 +3131,7 @@ block_param_def	: '|' opt_bv_decl '|' https://github.com/ruby/ruby/blob/trunk/parse.y#L3131
 		    }
 		| tOROP
 		    {
+			p->max_numparam = -1;
 		    /*%%%*/
 			$$ = 0;
 		    /*% %*/
@@ -3126,6 +3140,7 @@ block_param_def	: '|' opt_bv_decl '|' https://github.com/ruby/ruby/blob/trunk/parse.y#L3140
 		| '|' block_param opt_bv_decl '|'
 		    {
 			p->cur_arg = 0;
+			p->max_numparam = -1;
 		    /*%%%*/
 			$$ = $2;
 		    /*% %*/
@@ -3357,20 +3372,34 @@ brace_block	: '{' brace_body '}' https://github.com/ruby/ruby/blob/trunk/parse.y#L3372
 		;
 
 brace_body	: {$<vars>$ = dyna_push(p);}
+		    {
+			$<num>$ = p->max_numparam;
+			p->max_numparam = 0;
+		    }
 		  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);
 		    }
 		;
 
 do_body 	: {$<vars>$ = dyna_push(p);}
-		  {CMDARG_PUSH(0);}
+		    {
+			$<num>$ = p->max_numparam;
+			p->max_numparam = 0;
+			CMDARG_PUSH(0);
+		    }
 		  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, &@$);
 		    /*% %*/
@@ -3773,6 +3802,13 @@ string_dvar	: tGVAR https://github.com/ruby/ruby/blob/trunk/parse.y#L3802
 		    /*% %*/
 		    /*% ripper: var_ref!($1) %*/
 		    }
+		| tNUMPARAM
+		    {
+		    /*%%%*/
+			$$ = NEW_DVAR(numparam_id(p, $1), &@1);
+		    /*% %*/
+		    /*% ripper: var_ref!(number_arg!($1)) %*/
+		    }
 		| backref
 		;
 
@@ -3828,6 +3864,13 @@ user_variable	: tIDENTIFIER https://github.com/ruby/ruby/blob/trunk/parse.y#L3864
 		| tGVAR
 		| tCONSTANT
 		| tCVAR
+		| tNUMPARAM
+		    {
+		    /*%%%*/
+			$$ = numparam_id(p, $1);
+		    /*% %*/
+		    /*% ripper: number_arg!($1) %*/
+		    }
 		;
 
 keyword_variable: keyword_nil {$$ = KWD2EID(nil, $1);}
@@ -5967,6 +6010,9 @@ parser_peek_variable_name(struct parser_ https://github.com/ruby/ruby/blob/trunk/parse.y#L6010
 	    if (++ptr >= p->lex.pend) return 0;
 	    c = *ptr;
 	}
+	else if (ISDIGIT(c)) {
+	    return tSTRING_DVAR;
+	}
 	break;
       case '{':
 	p->lex.pcur = ptr;
@@ -7612,12 +7658,34 @@ parse_atmark(struct parser_params *p, co https://github.com/ruby/ruby/blob/trunk/parse.y#L7658
 	return 0;
     }
     else if (ISDIGIT(c)) {
-	RUBY_SET_YYLLOC(loc);
-	pushback(p, c);
 	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 {
+	    RUBY_SET_YYLLOC(loc);
+	    pushback(p, c);
 	    compile_error(p, "`@@%c' is not allowed as a class variable name", c);
 	}
 	parser_show_error_line(p, &loc);
@@ -8876,6 +8944,12 @@ gettable(struct parser_params *p, ID id, https://github.com/ruby/ruby/blob/trunk/parse.y#L8944
 	return NEW_LIT(add_mark_object(p, rb_enc_from_encoding(p->enc)), loc);
     }
     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:
 	if (dyna_in_block(p) && dvar_defined_ref(p, id, &vidp)) {
 	    if (id == p->cur_arg) {
@@ -9331,6 +9405,15 @@ assignable0(struct parser_params *p, ID https://github.com/ruby/ruby/blob/trunk/parse.y#L9405
 	*err = "dynamic constant assignment";
 	return -1;
       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:
 	compile_error(p, "identifier %"PRIsVALUE" is not valid to set", rb_id2str(id));
     }
@@ -9355,6 +9438,21 @@ assignable(struct parser_params *p, ID i https://github.com/ruby/ruby/blob/trunk/parse.y#L9438
     if (err) yyerror1(loc, err);
     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
 static VALUE
 assignable(struct parser_params *p, VALUE lhs)
@@ -10227,6 +10325,17 @@ new_args_tail(struct parser_params *p, N https://github.com/ruby/ruby/blob/trunk/parse.y#L10325
     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*
 dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc)
 {
Index: ext/ripper/eventids2.c
===================================================================
--- ext/ripper/eventids2.c	(revision 67277)
+++ ext/ripper/eventids2.c	(revision 67278)
@@ -51,6 +51,7 @@ typedef struct { https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L51
     ID ripper_id_label_end;
     ID ripper_id_tlambda;
     ID ripper_id_tlambeg;
+    ID ripper_id_tnumparam;
 
     ID ripper_id_ignored_nl;
     ID ripper_id_comment;
@@ -113,6 +114,7 @@ ripper_init_eventids2(void) https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L114
     set_id2(label_end);
     set_id2(tlambda);
     set_id2(tlambeg);
+    set_id2(tnumparam);
 
     set_id2(ignored_nl);
     set_id2(comment);
@@ -276,6 +278,7 @@ static const struct token_assoc { https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L278
     {tLABEL_END,		O(label_end)},
     {tLAMBDA,			O(tlambda)},
     {tLAMBEG,			O(tlambeg)},
+    {tNUMPARAM, 		O(tnumparam)},
 
     /* ripper specific tokens */
     {tIGNORED_NL,		O(ignored_nl)},
Index: NEWS
===================================================================
--- NEWS	(revision 67277)
+++ NEWS	(revision 67278)
@@ -25,6 +25,9 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L25
 * Non-Symbol keys in a keyword arguments hash was prohibited at 2.6.0, but
   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)
 
 Enumerable::
Index: bootstraptest/test_syntax.rb
===================================================================
--- bootstraptest/test_syntax.rb	(revision 67277)
+++ bootstraptest/test_syntax.rb	(revision 67278)
@@ -532,7 +532,7 @@ end https://github.com/ruby/ruby/blob/trunk/bootstraptest/test_syntax.rb#L532
 assert_syntax_error "unterminated string meets end of file", '().."', '[ruby-dev:29732]'
 assert_equal %q{[]}, %q{$&;[]}, '[ruby-dev:31068]'
 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 set", %q{0..$00=1}
 assert_equal %q{0}, %q{[*0];0}, '[ruby-dev:31102]'

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]