|rcr|.xv Index Variables ( *_with_index )
From:
"daz" <dooby@...10.karoo.co.uk>
Date:
2003-09-02 14:48:23 UTC
List:
ruby-core #1495
Hello 'core,
My first post here, so I'll try not to be too controversial.
It might be too long for busy people.
=begin
[RCR] proposal - optional block index variable
==============================================
* Applies to all iterators, including future additions.
* No new methods. "with_index" depleted.
* Problem of naming (map_with_indexes/indices) disappears.
* No code breakage.
"A Language shouldn't get in your way"
* Removes an area where IMO it does.
=end
=begin
Quoting David Black from ruby-talk.org/57500,
If we had map_with_indices, you could do:
myarray.map_with_indices {|e,i| e + ", " if i % 2 == 0}.compact
but we don't. (Hint, hint.)
End quote.
myarray.map {|e|.i e + ", " if i % 2 == 0}.compact
=end
=begin
Private letter to the President of Citizens for MWI, Inc.
Dear Dr. Black,
I think the time has arrived for the final push.
We know where he lives. We have his photograph ;-/
All we need now is camping equipment and hot soup.
Let's violate PoLS !
Yours faithfully,
A. Citizen.
=end
=================================================================
Ruby examples: ruby 1.8.0 (2003-08-30+) [i586-bccwin32]
=================================================================
----------8<-----------------------------------------------------
a = ('a'..'z').to_a
a.map! {|x|.n ' ' + x + (n+1).to_s if n % 2 == 0}
a.display
#-> a1 c3 e5 g7 i9 k11 m13 o15 q17 s19 u21 w23 y25
----------8<----------------------------------------->8----------
loop {.x
x < 5 or break
p x
}
----------8<----------------------------------------->8----------
=begin
"each_byte_with_index" (etc.) doesn't exist.
=end
'Hello'.each_byte {|ch|.ix puts ch}
#-> 72
#-> 101
#-> 108
#-> 108
#-> 111
----------8<----------------------------------------->8----------
#
# Until each_with_index gets flushed
# down the toilet, we could do ...
#
# each_with_index (with index) Mmm, sounds nice.
(10..16).each_with_index do |n, wx|.index
p [n, wx, index]
raise 'Oh-Oh!' if index != wx
end
#-> [10, 0, 0]
#-> [11, 1, 1]
#-> [12, 2, 2]
#-> [13, 3, 3]
#-> [14, 4, 4]
#-> [15, 5, 5]
#-> [16, 6, 6]
----------8<----------------------------------------->8----------
10.times {|n|.n n+1}
#-> C:/TEMP/rbA115.TMP: 2: duplicate name for block parameter / index variable
#-> 10.times {|n|.n n+1}
#-> ^
----------8<----------------------------------------->8----------
y = 10
flg = 'a'
2.times do | z2 |.x
3.times do | z3 |.y
puts " #{z2}#{x}, #{z3}#{y}, (#{flg})"
if [z2, x, z3, y, flg] == [1,1,1,1,'a']
flg = 'c'
puts '* state change *'
retry # retry/redo
end
puts '-'*15
end
end
puts "final y = #{y}"
#-> 00, 00, (a)
#-> ---------------
#-> 00, 11, (a)
#-> ---------------
#-> 00, 22, (a)
#-> ---------------
#-> 11, 00, (a)
#-> ---------------
#-> 11, 11, (a)
#-> * state change *
#-> 11, 00, (c)
#-> ---------------
#-> 11, 11, (c)
#-> ---------------
#-> 11, 22, (c)
#-> ---------------
#-> final y = 2
=begin
From: http://zem.novylen.net/ruby/withindex.rb
module Enumerable
def method_missing(meth, *args, &block)
if meth.to_s =~ /_with_index/
m = meth.to_s.gsub!('_with_index','')
i = -1
self.send(m,*args) {|n|
i = i+1
block.call(n,i)
}
end
end
end
=end
=begin
From: http://www.loveruby.net/~aamine/ja/tdiary/20021210.html
# (extract of translation, edited)
unless [ ].respond_to?(:Sort_by)
class Array
def sort_by
list = [ ]
each_with_index do |i, idx|
list.push( [ yield(i), (idx and i) ] )
end
list.sort.map { |tmp, (idx and i)| i }
end
end
end
# When doing such, the map_with_index
# (or the Iterator) it becomes desired.
#
#(11:49)
=end
----------8<----------------------------------------->8----------
# ============================================================
# Extension for experimentalists - $XITER (silly temp. name)
# ============================================================
# You can use $XITER virtual_variable where block_given? applies.
# (Safe anywhere. Effect will be correct, even if you disagree;)
def x(q)
3.times do |i|.ix
printf("\nIter #%i==%i (%i)\n", i, ix, $XITER)
$XITER += 2
yield q, i, ix, $XITER
end
end
x ('roo') do |yq, yi, yix, yiter|.xv
p [[yq, yi, yix], [yiter, xv], $XITER]
end
#-> Iter #0==0 (0)
#-> [["roo", 0, 0], [2, 2], false]
#->
#-> Iter #1==1 (3)
#-> [["roo", 1, 1], [5, 5], false]
#->
#-> Iter #2==2 (6)
#-> [["roo", 2, 2], [8, 8], false]
----------8<----------------------------------------->8----------
=begin
Reasons why I left Ruby for another language
============================================
Ruby has powerful iterators but no access to their indexices.
So I changed to Ruby, which has both! C OO L.
Or should that be indicexes ???
Now my mind is free to explore inject(i) {|a,b|.x 'wheeesh'}
History (for power readers)
===========================
[CHANGELOG]
Thu Feb 5 18:58:46 1998 Yukihiro Matsumoto <matz@netlab.co.jp>
* enum.c (enum_each_with_index): new method.
http://www.ruby-talk.org/56121 DAB - MWI, Inc.
http://www.ruby-talk.org/56125 Matz - YAGNI
http://www.ruby-talk.org/56719 Matz
http://www.ruby-talk.org/56730 Matz
http://www.ruby-talk.org/56845 _why
http://www.ruby-talk.org/56939 extending block w/ counter
=end
----------8<----------------------------------------->8----------
# Currently difficult ?
"Hello World!".scan(/[aeiou]./) do |str|.ix
printf("(%2s) @ %2d\n", str, ix)
end
#-> (el) @ 1
#-> (o ) @ 4
#-> (or) @ 7
----------------------------------------------------->8----------
=================================================================
Implementation Notes
=================================================================
<parse.y>
1) The index variable (xv) is recognised by the parser in a
similar way to a block parameter.
2) An assignment node is created (L/DASGN) depending on
whether the xv already exists in an outer scope.
3) When an ITER node is created by the parser and an xv has
been recognised, pointers to the ASGN node and the ITER
node are stored in an ITERX node.
(It would have been easier to extend NODE_ITER by the size
of a pointer but that could, possibly, extend all ruby
objects.)
3a) Without xv: (Normal Ruby)
NODE_ITER
u1 'nd_var' (block params)
u2 'nd_body' (block statements)
u3 'nd_iter' (func; e.g. each)
3b) With xv:
NODE_ITERX
u1 'nd_inode' (-> ITER node)
u2 'nd_xvasn' (-> L/DASGN) [would be in u4]
u3 'nd_iter' (func; e.g. each)
NODE_ITER (from 'nd_inode' of ITERX above)
u1 'nd_var' (block params)
u2 'nd_body' (block statements)
u3 0
=================================================================
<eval.c>
4) When rb_eval sees an ITERX node and the struct BLOCK is
established, a pointer to the ASGN node is set in the
block for easy access and is used in tests for xv presence.
(Access to the ASGN node via the ITERX node may be lost
after a ruby 'redo' / 'retry' etc. Access is maintained
via the pointer in the BLOCK.)
[struct BLOCK is necessarily extended by 4 bytes.]
The value (index count) in the ASGN node is initialised
to zero at this time.
5) On a yield to the block (rb_yield_0), the value in the
ASGN node (which also holds the variable's ID) is assigned
to the index variable.
6) Before rb_yield_0 returns, the index count is incremented
but will be assigned to the variable only at the start of
the next iteration.
=================================================================
* Any assignment to the xv from a script will NOT be carried
over to the next iteration.
* Any iterator method implemented by the interpreter will be
able to alter the index value before the next iteration.
For example, String#scan could send the index position of
each match (currently inaccessible ?) into the block, if
an xv has been supplied, simply by overwriting the new
index value before a yield, using ...
void rb_xv_assign_long(long);
String#scan is the only iterator method that has been
tampered with. Unmodified, an xv in the block would
increment from zero like any other but it seemed that
selected methods might be usefully tweaked ? (e.g. gsub)
=================================================================
Any need for this ?? (Perhaps in Japan ?)
(Please pile on the pressure if you need similar fun ;)
[xv.patch] attached for experiments. (CVS head 2003/09/02)
(c) Dave Butcher - 2003/09/02 (10:10 +0100)
attachment is Ruby Licensed
Feel free to criticise but be forewarned that grumbling
about syntax could result in public humiliation from
a prepared, bullet-proof defence (defense) :-)
Thanks:
Matz - Ruby
Nobu - (for helping poor Win32 users) & speed-patching.
Ruby Hackers / Library authors.
PragDave Thomas - PickAxe & nodeDump & ri & stuff
Programming Ruby ... (K&R === T&H #-> true)
PragAndy Hunt - PickAxe & one-click installer
Sakazuki-san + - RDE (Ruby Development Environment) :(Win32 only)
Guy Decoux - 部ude posting & fl/ii (nodedump)
comp.lang.ruby / ruby-talk regulars.
David Alan Black - Code-brevity, brick wall head-banging &
English language guardianship.
daz
Attachments (1)
xv.patch
(10.8 KB, text/x-diff)
diff -u3pPr orig-1.8.0-2003.09.02/eval.c orxv-1.8.0-2003.09.02/eval.c
--- orig-1.8.0-2003.09.02/eval.c Tue Sep 02 06:52:36 2003
+++ orxv-1.8.0-2003.09.02/eval.c Tue Sep 02 13:10:30 2003
@@ -106,6 +106,10 @@ static VALUE rb_cUnboundMethod;
static VALUE umethod_bind _((VALUE, VALUE));
static VALUE rb_mod_define_method _((int, VALUE*, VALUE));
+static VALUE xv_getter _((void)); /* bXV */
+static void xv_setter _((VALUE)); /* bXV */
+extern void rb_xv_assign_long _((long)); /* bXV */
+
static int scope_vmode;
#define SCOPE_PUBLIC 0
#define SCOPE_PRIVATE 1
@@ -611,6 +615,7 @@ struct BLOCK {
VALUE klass;
NODE *cref;
int iter;
+ NODE *xvasn; /* xv ASGN node ptr bXV */
int vmode;
int flags;
struct RVarmap *dyna_vars;
@@ -639,6 +644,7 @@ static struct BLOCK *ruby_block;
_block.prev = ruby_block; \
_block.outer = ruby_block; \
_block.iter = ruby_iter->iter; \
+ _block.xvasn = 0; /* bXV */ \
_block.vmode = scope_vmode; \
_block.flags = BLOCK_D_SCOPE; \
_block.dyna_vars = ruby_dyna_vars; \
@@ -2440,6 +2446,7 @@ rb_eval(self, n)
if (!node) RETURN(Qnil);
ruby_current_node = node;
+
switch (nd_type(node)) {
case NODE_BLOCK:
if (contnode) {
@@ -2687,6 +2694,7 @@ rb_eval(self, n)
result = block_pass(self, node);
break;
+ case NODE_ITERX:
case NODE_ITER:
case NODE_FOR:
{
@@ -2697,7 +2705,16 @@ rb_eval(self, n)
if (state == 0) {
iter_retry:
PUSH_ITER(ITER_PRE);
- if (nd_type(node) == NODE_ITER) {
+
+ if (nd_type(node) == NODE_ITERX) { /* bXV */
+ ruby_block->var = node->nd_inode->nd_var;
+ ruby_block->body = node->nd_inode->nd_body;
+ ruby_block->xvasn = node->nd_xvasn;
+ ruby_block->xvasn->nd_xvval = INT2FIX(0); /* bXV Initialize index (zero by convention ;) */
+
+ result = rb_eval(self, node->nd_iter);
+ }
+ else if (nd_type(node) == NODE_ITER) {
result = rb_eval(self, node->nd_iter);
}
else {
@@ -3766,6 +3783,7 @@ rb_mod_protected_method_defined(mod, mid
}
NORETURN(static VALUE terminate_process _((int, const char *, long)));
+
static VALUE
terminate_process(status, mesg, mlen)
int status;
@@ -4081,6 +4099,7 @@ rb_yield_0(val, self, klass, flags, aval
ruby_dyna_vars = block->dyna_vars;
}
PUSH_CLASS(klass ? klass : block->klass);
+
if (!klass) {
self = block->self;
}
@@ -4089,6 +4108,7 @@ rb_yield_0(val, self, klass, flags, aval
if (block->var) {
PUSH_TAG(PROT_NONE);
if ((state = EXEC_TAG()) == 0) {
+
if (block->var == (NODE*)1) { /* no parameter || */
if ((flags & YIELD_PROC_CALL) && RARRAY(val)->len != 0) {
rb_raise(rb_eArgError, "wrong number of arguments (%ld for 0)",
@@ -4140,6 +4160,10 @@ rb_yield_0(val, self, klass, flags, aval
if (state) goto pop_state;
}
+ if (block->xvasn) { /* bXV */
+ assign(0, block->xvasn, block->xvasn->nd_xvval, 0); /* bXV */
+ }
+
PUSH_ITER(block->iter);
PUSH_TAG(PROT_NONE);
if ((state = EXEC_TAG()) == 0) {
@@ -4209,9 +4233,44 @@ rb_yield_0(val, self, klass, flags, aval
scope_vmode = old_vmode;
ruby_current_node = cnode;
if (state) JUMP_TAG(state);
+
+ if (block->xvasn) {
+ /* bXV - increment index for next iteration */
+ block->xvasn->nd_xvval = INT2FIX((FIX2INT(block->xvasn->nd_xvval))+1); /* bXV */
+ }
+
return result;
}
+static VALUE
+xv_getter() /* bXV */
+{
+ if (rb_block_given_p()) {
+ if (ruby_block->xvasn) {
+ return ruby_block->xvasn->nd_xvval;
+ }
+ }
+ return Qfalse;
+}
+
+static void
+xv_setter(val) /* bXV */
+ VALUE val;
+{
+ if (FIXNUM_P(val) && rb_block_given_p()) {
+ if (ruby_block->xvasn) {
+ ruby_block->xvasn->nd_xvval = val;
+ }
+ }
+}
+
+extern void
+rb_xv_assign_long(val) /* bXV */
+ long val;
+{
+ xv_setter(INT2FIX(val));
+}
+
VALUE
rb_yield(val)
VALUE val;
@@ -6527,6 +6586,7 @@ Init_eval()
rb_define_virtual_variable("$@", errat_getter, errat_setter);
rb_define_hooked_variable("$!", &ruby_errinfo, 0, errinfo_setter);
+ rb_define_virtual_variable("$XITER", xv_getter, xv_setter); /* bXV */
rb_define_global_function("eval", rb_f_eval, -1);
rb_define_global_function("iterator?", rb_f_block_given_p, 0);
diff -u3pPr orig-1.8.0-2003.09.02/gc.c orxv-1.8.0-2003.09.02/gc.c
--- orig-1.8.0-2003.09.02/gc.c Fri Aug 22 09:09:56 2003
+++ orxv-1.8.0-2003.09.02/gc.c Mon Sep 01 08:26:20 2003
@@ -675,6 +675,7 @@ rb_gc_mark_children(ptr)
case NODE_IF: /* 1,2,3 */
case NODE_FOR:
case NODE_ITER:
+ case NODE_ITERX: /* bXV */
case NODE_CREF:
case NODE_WHEN:
case NODE_MASGN:
diff -u3pPr orig-1.8.0-2003.09.02/node.h orxv-1.8.0-2003.09.02/node.h
--- orig-1.8.0-2003.09.02/node.h Wed Aug 27 20:43:46 2003
+++ orxv-1.8.0-2003.09.02/node.h Tue Sep 02 01:26:18 2003
@@ -29,6 +29,7 @@ enum node_type {
NODE_OPT_N,
NODE_WHILE,
NODE_UNTIL,
+ NODE_ITERX,
NODE_ITER,
NODE_FOR,
NODE_BREAK,
@@ -194,6 +195,11 @@ typedef struct RNode {
#define nd_ibdy u2.node
#define nd_iter u3.node
+#define nd_inode u1.node /* bXV -> NODE_ITER */
+#define nd_xvasn u2.node /* bXV -> NODE_?ASGN */
+
+#define nd_xvval u2.value /* bXV index value */
+
#define nd_value u2.node
#define nd_aid u3.id
@@ -252,6 +258,7 @@ typedef struct RNode {
#define NEW_UNTIL(c,b,n) NEW_NODE(NODE_UNTIL,c,b,n)
#define NEW_FOR(v,i,b) NEW_NODE(NODE_FOR,v,b,i)
#define NEW_ITER(v,i,b) NEW_NODE(NODE_ITER,v,b,i)
+#define NEW_ITERX(inode,xvasn) NEW_NODE(NODE_ITERX,inode,xvasn,0)
#define NEW_BREAK(s) NEW_NODE(NODE_BREAK,s,0,0)
#define NEW_NEXT(s) NEW_NODE(NODE_NEXT,s,0,0)
#define NEW_REDO() NEW_NODE(NODE_REDO,0,0,0)
diff -u3pPr orig-1.8.0-2003.09.02/parse.y orxv-1.8.0-2003.09.02/parse.y
--- orig-1.8.0-2003.09.02/parse.y Tue Sep 02 06:50:46 2003
+++ orxv-1.8.0-2003.09.02/parse.y Tue Sep 02 11:13:12 2003
@@ -61,6 +61,8 @@ NODE *ruby_eval_tree = 0;
char *ruby_sourcefile; /* current source file */
int ruby_sourceline; /* current line no. */
+int xv_line; /* bXV line no. */
+
static int yylex();
static int yyerror();
@@ -260,6 +262,7 @@ static void top_local_setup();
%type <node> f_arglist f_args f_optarg f_opt f_block_arg opt_f_block_arg
%type <node> assoc_list assocs assoc undef_list backref string_dvar
%type <node> block_var opt_block_var brace_block cmd_brace_block do_block lhs none
+%type <node> opt_block_index var_index paren_var_index /* bXV */
%type <node> mlhs mlhs_head mlhs_basic mlhs_entry mlhs_item mlhs_node
%type <id> fitem variable sym symbol operation operation2 operation3
%type <id> cname fname op f_rest_arg
@@ -651,13 +654,15 @@ block_command : block_call
cmd_brace_block : tLBRACE_ARG
{
$<vars>$ = dyna_push();
- $<num>1 = ruby_sourceline;
+ $<num>1 = xv_line = ruby_sourceline;
}
- opt_block_var {$<vars>$ = ruby_dyna_vars;}
+ opt_block_var {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;} /* bXV */
+ opt_block_index {$<vars>4 = ruby_dyna_vars;} /* bXV */
compstmt
'}'
{
- $$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+ $$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+ if ($5) $$ = NEW_ITERX($$, $5); /* bXV */
nd_set_line($$, $<num>1);
dyna_pop($<vars>2);
}
@@ -1733,16 +1738,44 @@ opt_block_var : none
}
;
+paren_var_index : '(' /* none */ ')'
+ {
+ $$ = 0;
+ }
+ | '(' var_index ')'
+ {
+ $$ = $2;
+ }
+ ;
+
+opt_block_index : none
+ | '.' var_index
+ {
+ if (xv_line != ruby_sourceline) goto xv_Err_lbfd;
+ $$ = $2;
+ }
+ | '.' paren_var_index
+ {
+ if (xv_line != ruby_sourceline) {
+ xv_Err_lbfd:
+ rb_warn("break in line %i", xv_line);
+ }
+ $$ = $2;
+ }
+ ;
+
do_block : kDO_BLOCK
{
$<vars>$ = dyna_push();
- $<num>1 = ruby_sourceline;
+ $<num>1 = xv_line = ruby_sourceline;
}
- opt_block_var {$<vars>$ = ruby_dyna_vars;}
+ opt_block_var {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;} /* bXV */
+ opt_block_index {$<vars>4 = ruby_dyna_vars;} /* bXV */
compstmt
kEND
{
- $$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+ $$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+ if ($5) $$ = NEW_ITERX($$, $5); /* bXV */
nd_set_line($$, $<num>1);
dyna_pop($<vars>2);
}
@@ -1799,24 +1832,28 @@ method_call : operation paren_args
brace_block : '{'
{
$<vars>$ = dyna_push();
- $<num>1 = ruby_sourceline;
+ $<num>1 = xv_line = ruby_sourceline;
}
- opt_block_var {$<vars>$ = ruby_dyna_vars;}
+ opt_block_var {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;} /* bXV */
+ opt_block_index {$<vars>4 = ruby_dyna_vars;} /* bXV */
compstmt '}'
{
- $$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+ $$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+ if ($5) $$ = NEW_ITERX($$, $5); /* bXV */
nd_set_line($$, $<num>1);
dyna_pop($<vars>2);
}
| kDO
{
$<vars>$ = dyna_push();
- $<num>1 = ruby_sourceline;
+ $<num>1 = xv_line = ruby_sourceline;
}
- opt_block_var {$<vars>$ = ruby_dyna_vars;}
+ opt_block_var {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;} /* bXV */
+ opt_block_index {$<vars>4 = ruby_dyna_vars;} /* bXV */
compstmt kEND
{
- $$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+ $$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+ if ($5) $$ = NEW_ITERX($$, $5); /* bXV */
nd_set_line($$, $<num>1);
dyna_pop($<vars>2);
}
@@ -2160,6 +2197,33 @@ var_lhs : variable
}
;
+var_index : tCONSTANT { goto xv_Err_bimbl; }
+ | tIVAR { goto xv_Err_bimbl; }
+ | tGVAR { goto xv_Err_bimbl; }
+ | tCVAR { goto xv_Err_bimbl; }
+ | tIDENTIFIER
+ {
+ if (!is_local_id($1)) {
+ xv_Err_bimbl:
+ yyerror("block index must be a local variable");
+ }
+ if (rb_dvar_curr($1)) {
+ yyerror("duplicate name for block parameter / index variable");
+ }
+
+ if (rb_dvar_defined($1)) {
+ $$ = NEW_DASGN($1, 0);
+ }
+ else if (local_id($1) || !dyna_in_block()) {
+ $$ = NEW_LASGN($1, 0);
+ }
+ else{
+ rb_dvar_push($1, 0);
+ $$ = NEW_DASGN_CURR($1, 0);
+ }
+ }
+ ;
+
backref : tNTH_REF
| tBACK_REF
;
@@ -2541,6 +2605,7 @@ yycompile(f, line)
n = yyparse();
ruby_debug_lines = 0;
compile_for_eval = 0;
+
ruby_in_compile = 0;
cond_stack = 0;
cmdarg_stack = 0;
diff -u3pPr orig-1.8.0-2003.09.02/string.c orxv-1.8.0-2003.09.02/string.c
--- orig-1.8.0-2003.09.02/string.c Wed Aug 27 20:43:46 2003
+++ orxv-1.8.0-2003.09.02/string.c Sat Aug 30 20:38:30 2003
@@ -3066,6 +3066,7 @@ rb_str_scan(str, pat)
while (!NIL_P(result = scan_once(str, pat, &start))) {
match = rb_backref_get();
rb_match_busy(match);
+ rb_xv_assign_long(start - RSTRING(result)->len); /* bXV trick */
rb_yield(result);
rb_backref_set(match); /* restore $~ value */
}