PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/perl5/Pugs-Runtime-Value/lib/Pugs/Runtime/Value/List.pm

http://pugs.googlecode.com/
Perl | 587 lines | 459 code | 60 blank | 68 comment | 82 complexity | ca09f3d2e456c0c81e3856a9e55c83da MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-3.0, GPL-2.0, LGPL-2.1
  1. package Pugs::Runtime::Value::List;
  2. # Pugs::Runtime::Value::List - implementation of Perl6 'List' class in Perl5
  3. # ChangeLog
  4. #
  5. # 2005-09-05
  6. # * removed uniq(), grep() - now implemented in p6 Prelude
  7. # * map() uses Code->arity
  8. # * lists have an internal buffer for unshift/push
  9. #
  10. # 2005-09-01
  11. # * fixed stringification
  12. #
  13. # 2005-08-31
  14. # * str() is non-destructive
  15. #
  16. # 2005-08-30
  17. # * new hook 'clone'
  18. # * some constructors implement deep cloning
  19. # * functions that don't support deep cloning emit warnings on clone
  20. #
  21. # 2005-08-29
  22. # * new methods shift_n() pop_n() - provide lazy access to sublists
  23. # * new hook DESTROY
  24. #
  25. # 2005-08-27
  26. # * new methods start() end()
  27. #
  28. # 2005-08-23
  29. # * fixed stringification
  30. #
  31. # 2005-08-12
  32. # * fixed map()->pop()
  33. #
  34. # 2005-08-11
  35. # * Fixed string comparison to Inf portability (Windows x Linux)
  36. # * Separate from_num_range() and from_range() constructors.
  37. # - from_num_range() is a numeric range. It accepts a 'step' value.
  38. # - from_range() is a generic range for strings, etc. It accepts a 'celems' closure.
  39. # Both constructors are just new() wrappers.
  40. # * grep(), map() don't depend on coroutines
  41. # * Removed pair() - this module does not have access to the Pair constructor
  42. #
  43. # 2005-08-10
  44. # * Removed method concat_list(), added "TODO" methods
  45. # TODO - finish sum() - not all operations support sum() yet;
  46. # - object numification is not supported yet
  47. # TODO - finish support for unshift, push
  48. # TODO - List.is_lazy() could be defined with a closure; Perl6 version too
  49. # TODO - map(), grep() could accept the optional 'celems' parameter - for kv() implementation
  50. # TODO - is_contiguous() should test if $step == 1
  51. use strict;
  52. use Pugs::Runtime::Value;
  53. use constant Inf => Pugs::Runtime::Value::Num::Inf;
  54. our $VERSION = '0.02';
  55. sub _default_stringify {
  56. my $self = (shift)->clone;
  57. my @start;
  58. my @end;
  59. my $samples = 3;
  60. $samples = 1000 unless $self->is_infinite;
  61. my $tmp = -&Inf;
  62. for ( 1 .. $samples ) {
  63. last unless $self->elems;
  64. $tmp = $self->shift;
  65. last if $tmp == Inf;
  66. push @start, $tmp;
  67. last if $tmp == -&Inf;
  68. }
  69. $tmp = Inf;
  70. for ( 1 .. $samples ) {
  71. last unless $self->elems;
  72. $tmp = $self->pop( $tmp );
  73. last if $tmp == -&Inf;
  74. unshift @end, $tmp;
  75. last if $tmp == Inf;
  76. }
  77. return '' unless @start;
  78. # if @start and @end intersect, don't print ".."
  79. if ( $self->elems == 0 ) {
  80. push @start, @end;
  81. return join( ', ', @start, @end );
  82. }
  83. return
  84. join( ', ',
  85. map { Pugs::Runtime::Value::stringify( $_ ) } @start ) .
  86. ' ... ' .
  87. join( ', ',
  88. map { Pugs::Runtime::Value::stringify( $_ ) } @end );
  89. }
  90. sub new {
  91. my $class = shift;
  92. my %param = @_;
  93. $param{cis_infinite} = sub { $_[0]->{celems}() == Inf }
  94. unless defined $param{cis_infinite};
  95. $param{cis_contiguous} = sub { 0 }
  96. unless defined $param{cis_contiguous};
  97. $param{cstringify} = \&_default_stringify
  98. unless defined $param{cstringify};
  99. $param{is_lazy} = 1 unless defined $param{is_lazy};
  100. unless ( defined $param{celems} ) {
  101. $param{celems} =
  102. ( defined $param{cstart} || defined $param{cend} ) ? sub { Inf } : sub { 0 }
  103. }
  104. $param{cstart} = sub {} unless defined $param{cstart};
  105. $param{cend} = sub {} unless defined $param{cend};
  106. $param{start} = sub {} unless defined $param{start};
  107. $param{end} = sub {} unless defined $param{end};
  108. $param{shift_n} = sub { $_[0]->shift } unless defined $param{shift_n};
  109. $param{pop_n} = sub { $_[0]->pop } unless defined $param{pop_n};
  110. $param{DESTROY} = sub {} unless defined $param{DESTROY};
  111. $param{clone} = sub {
  112. my $self = shift;
  113. my $clone = bless { %$self }, ref $self;
  114. @{$clone->{pops}} = @{$self->{pops}};
  115. @{$clone->{shifts}} = @{$self->{shifts}};
  116. return $clone;
  117. } unless defined $param{clone};
  118. $param{sum} = sub { undef } unless defined $param{sum};
  119. $param{shifts} = [] unless defined $param{shifts};
  120. $param{pops} = [] unless defined $param{pops};
  121. return bless \%param, $class;
  122. }
  123. sub DESTROY { $_[0]->{DESTROY}( @_ ) }
  124. sub clone { $_[0]->{clone}( @_ ) }
  125. sub elems { $_[0]->{celems}( @_ ) }
  126. sub is_infinite { $_[0]->{cis_infinite}( @_ ) }
  127. sub is_contiguous { $_[0]->{cis_contiguous}( @_ ) }
  128. sub is_lazy { $_[0]->{is_lazy} }
  129. sub str { $_[0]->{cstringify}( @_ ) }
  130. sub int { $_[0]->elems }
  131. sub bit { $_[0]->elems > 0 }
  132. sub num { $_[0]->elems }
  133. sub perl { '(' . $_[0]->{cstringify}( @_ ) . ')' }
  134. sub shift_n { $_[0]->{shift_n}( @_ ) }
  135. sub pop_n { $_[0]->{pop_n}( @_ ) }
  136. sub sum { $_[0]->{sum}( @_ ) }
  137. sub flatten {
  138. my $ret = shift;
  139. my $class = ref($ret);
  140. # TODO - add tests for this error message
  141. # fail "can't instantiate an infinite list"
  142. # if $ret->is_infinite;
  143. my @list;
  144. while ( $ret->elems ) { push @list, $ret->shift }
  145. $class->from_single( @list );
  146. }
  147. sub from_num_range {
  148. my $class = shift;
  149. my %param = @_;
  150. my $start = Pugs::Runtime::Value::numify( $param{start} );
  151. my $end = Pugs::Runtime::Value::numify( $param{end} );
  152. my $step = Pugs::Runtime::Value::numify( $param{step} || 1 );
  153. $class->new(
  154. clone => sub {
  155. $class->from_num_range( start => $start, end => $end, step => $step )
  156. },
  157. sum => sub {
  158. $_[0]->elems * ( $start + $end ) / 2;
  159. },
  160. shift_n => sub {
  161. my $list = shift;
  162. my $length = shift;
  163. # warn "shift_n( $length ) from ". $list->start . "..". $list->end;
  164. my $middle = $start + $length - 1;
  165. $middle = $end if $middle > $end;
  166. my $shifted = ref($list)->from_num_range(
  167. start => $start,
  168. end => $middle,
  169. step => $step,
  170. );
  171. $start = $middle + 1;
  172. return $shifted;
  173. },
  174. start => sub { $start },
  175. end => sub { $end },
  176. cstart => sub {
  177. my $r = $start;
  178. if ( defined $step ) { $start += $step } else { $start++ };
  179. return $r;
  180. },
  181. cend => sub {
  182. my $r = $end;
  183. if ( defined $step ) {
  184. # XXX - this should use modulus, etc.
  185. $end -= $step
  186. }
  187. else {
  188. $end--
  189. };
  190. return $r;
  191. },
  192. celems => sub {
  193. # warn "ELEMS $end - $start = ".($end - $start + 1)."\n";
  194. return Inf if $start == -&Inf || $end == Inf;
  195. return $end - $start + 1 unless defined $step;
  196. return CORE::int(( $end - $start + 1 ) / $step);
  197. },
  198. cis_infinite => sub { return $start == -&Inf || $end == Inf },
  199. cis_contiguous => sub { $step == -1 || $step == 1 },
  200. );
  201. }
  202. sub from_range {
  203. my $class = shift;
  204. my %param = @_;
  205. my $start = $param{start};
  206. my $end = $param{end};
  207. my $count = $param{celems};
  208. $count = sub {
  209. no warnings 'numeric';
  210. if ( ref($end) ) {
  211. return Inf if $end->unboxed == Inf;
  212. return $start->unboxed le $end->unboxed ? Inf : 0
  213. }
  214. else
  215. {
  216. return Inf if $end == Inf;
  217. return $start le $end ? Inf : 0
  218. }
  219. }
  220. unless defined $count;
  221. $class->new(
  222. clone => sub {
  223. $class->from_range( start => $start, end => $end, celems => $count )
  224. },
  225. sum => sub {
  226. warn "string sum not supported";
  227. },
  228. start => sub { $start },
  229. end => sub { $end },
  230. cstart => sub {
  231. my $tmp = $start;
  232. if ( ref( $start ) ) {
  233. $start = $start->increment;
  234. }
  235. else {
  236. $start++;
  237. }
  238. $tmp;
  239. },
  240. cend => sub {
  241. my $tmp = $end;
  242. if ( ref( $end ) ) {
  243. $end = $end->decrement;
  244. }
  245. else {
  246. $end--;
  247. }
  248. $tmp;
  249. },
  250. celems => $count,
  251. cis_contiguous => sub { 1 },
  252. );
  253. }
  254. sub from_x {
  255. # implements ('a' x 100) style lists
  256. # this can be used to create sparse arrays like: [ 1, undef x 10000, 2 ]
  257. my $class = shift;
  258. my %param = @_;
  259. my $item = $param{item};
  260. my $count = $param{count};
  261. $count = 0
  262. unless defined $count;
  263. $class->new(
  264. clone => sub {
  265. $class->from_x( item => $item, count => $count )
  266. },
  267. sum => sub {
  268. Pugs::Runtime::Value::numify( $_[0] ) * $item;
  269. },
  270. shift_n => sub {
  271. my $list = shift;
  272. my $length = shift;
  273. # warn "shift_n( $length ) from ". $list->start . "..". $list->end;
  274. $length = $count if $length > $count;
  275. my $shifted = ref($list)->from_x(
  276. item => $item,
  277. count => $length,
  278. );
  279. $count = $count - $length;
  280. return $shifted;
  281. },
  282. start => sub { $item },
  283. end => sub { $item },
  284. cstart => sub { $count--; return if $count < 0; $item },
  285. cend => sub { $count--; return if $count < 0; $item },
  286. celems => sub { $count },
  287. );
  288. }
  289. sub from_single {
  290. my $class = shift;
  291. my @list;
  292. for( @_ ) {
  293. if ( UNIVERSAL::isa($_, 'Pugs::Runtime::Value::List') ) {
  294. my @li; push @li, $_->shift while $_->elems;
  295. push @list, @li;
  296. }
  297. else {
  298. push @list, $_
  299. }
  300. }
  301. $class->new(
  302. clone => sub { $class->from_single( @list ) },
  303. start => sub { $list[0] },
  304. end => sub { $list[-1] },
  305. cstart => sub { shift @list },
  306. cend => sub { pop @list },
  307. celems => sub { scalar @list },
  308. is_lazy => 0,
  309. );
  310. }
  311. sub from_coro {
  312. my $class = shift;
  313. my $start = shift;
  314. my $size = &Inf;
  315. $class->new(
  316. clone => sub {
  317. warn "from_coro->clone() not implemented";
  318. $class->from_coro( $start )
  319. },
  320. cstart => sub {
  321. my $r = $start->();
  322. # print "coro\n";
  323. $size = 0 unless defined $r;
  324. return $r;
  325. },
  326. cend => sub {},
  327. celems => sub { $size },
  328. cis_infinite => sub { $size == Inf },
  329. cis_contiguous => sub { 0 },
  330. );
  331. }
  332. # --- list operations ---
  333. sub reverse {
  334. my $ret = shift;
  335. Pugs::Runtime::Value::List->new(
  336. clone => sub {
  337. $ret->clone->reverse
  338. },
  339. sum => $ret->{sum},
  340. start => $ret->{end},
  341. end => $ret->{start},
  342. cstart => $ret->{cend},
  343. cend => $ret->{cstart},
  344. celems => $ret->{celems},
  345. cis_infinite => $ret->{cis_infinite},
  346. cis_contiguous => $ret->{cis_contiguous},
  347. cstringify => $ret->{cstringify},
  348. );
  349. }
  350. sub map {
  351. my $array = shift;
  352. my $code = shift;
  353. my $ret = $array->clone;
  354. my $arity = Pugs::Runtime::Value::numify( $code->arity );
  355. Pugs::Runtime::Value::List->new(
  356. clone => sub {
  357. my $self = shift;
  358. # this doesn't work if $code is a closure
  359. # it may break grep(), uniq(), kv()...
  360. # It will need $code->clone in order to work...
  361. warn "List::map->clone() not implemented";
  362. warn "map->clone has pending shifts/pops" if @{$self->{pops}} || @{$self->{shifts}};
  363. $ret->clone->map( $code )
  364. },
  365. cstart => sub {
  366. my $self = shift;
  367. # print "entering map, elems = ", $ret->elems, "\n";
  368. while( $ret->elems && @{$self->{shifts}} < 2 ) {
  369. my @x;
  370. push @x, $ret->shift for 1 .. $arity;
  371. my $res = $code->do( @x );
  372. my @res;
  373. if ( Pugs::Runtime::Value::p6v_isa( $res, 'Array' ) ) {
  374. @res = $res->items
  375. }
  376. else {
  377. @res = ($res)
  378. }
  379. push @{$self->{shifts}}, @res;
  380. }
  381. # print " left [", @shifts, @pops, "] ", scalar @shifts, "+", scalar @pops, "\n";
  382. return shift @{$self->{shifts}} if @{$self->{shifts}};
  383. return shift @{$self->{pops}} if @{$self->{pops}};
  384. return
  385. },
  386. cend => sub {
  387. my $self = shift;
  388. while( $ret->elems && @{$self->{pops}} < 2 ) {
  389. my @x;
  390. unshift @x, $ret->pop for 1 .. $arity;
  391. my $res = $code->do( @x );
  392. my @res;
  393. if ( Pugs::Runtime::Value::p6v_isa( $res, 'Array' ) ) {
  394. @res = $res->items
  395. }
  396. else {
  397. @res = ($res)
  398. }
  399. unshift @{$self->{pops}}, @res;
  400. }
  401. return pop @{$self->{pops}} if @{$self->{pops}};
  402. return pop @{$self->{shifts}} if @{$self->{shifts}};
  403. return
  404. },
  405. celems => sub {
  406. my $self = shift;
  407. $ret->elems ? Inf : scalar @{$self->{shifts}} + scalar @{$self->{pops}}
  408. },
  409. );
  410. }
  411. sub zip {
  412. my $array = shift;
  413. my @lists = @_;
  414. my $ret = $array->clone;
  415. Pugs::Runtime::Value::List->new(
  416. sum => sub {
  417. my $sum = $ret->sum;
  418. $sum += $_->sum for @lists;
  419. return $sum;
  420. },
  421. clone => sub {
  422. my $self = shift;
  423. my ( $l, @ls ) = map { $_->clone } ( $ret, @lists );
  424. warn "zip->clone has pending shifts/pops" if @{$self->{pops}} || @{$self->{shifts}};
  425. return $l->zip( @ls );
  426. },
  427. cstart => sub {
  428. my $self = shift;
  429. return shift @{$self->{shifts}} if @{$self->{shifts}};
  430. my $any = 0;
  431. for ( $ret, @lists ) { $any++ if $_->elems }
  432. push @{$self->{shifts}}, ( $ret->shift,
  433. map { my $x = $_->shift } #; defined $x ? $x : 'x' }
  434. @lists ) if $any;
  435. return shift @{$self->{shifts}} if @{$self->{shifts}};
  436. return shift @{$self->{pops}} if @{$self->{pops}};
  437. return
  438. },
  439. cend => sub {
  440. my $self = shift;
  441. return pop @{$self->{pops}} if @{$self->{pops}};
  442. my $any = 0;
  443. for ( $ret, @lists ) { $any++ if $_->elems }
  444. unshift @{$self->{pops}}, ( $ret->pop,
  445. map { my $x = $_->pop } #; defined $x ? $x : 'x' }
  446. @lists ) if $any;
  447. return pop @{$self->{pops}} if @{$self->{pops}};
  448. return pop @{$self->{shifts}} if @{$self->{shifts}};
  449. return
  450. },
  451. celems => sub {
  452. my $self = shift;
  453. my $any = 0;
  454. for ( $ret, @lists ) { $any++ if $_->elems }
  455. $any ? Inf : scalar @{$self->{shifts}} + scalar @{$self->{pops}}
  456. },
  457. );
  458. }
  459. sub shift { $_[0]->{celems}( @_ ) ? $_[0]->{cstart}( @_ ) : undef }
  460. sub pop { $_[0]->{celems}( @_ ) ? $_[0]->{cend}( @_ ) : undef }
  461. sub start { $_[0]->{celems}( @_ ) ? $_[0]->{start}( @_ ) : undef }
  462. sub end { $_[0]->{celems}( @_ ) ? $_[0]->{end}( @_ ) : undef }
  463. 1;
  464. __END__
  465. # removed uniq, grep - these are just examples
  466. sub _MySub::arity { 1 };
  467. sub _MySub::do { (shift)->(@_) };
  468. sub uniq {
  469. # TODO - use p6 hash
  470. my $array = shift;
  471. my %seen = ();
  472. return $array->map(
  473. bless sub {
  474. my $str = $_[0];
  475. $str = '**UnDeF**' unless defined $str;
  476. return if $seen{$str};
  477. $seen{$str}++;
  478. $_[0];
  479. }, '_MySub' );
  480. }
  481. sub grep {
  482. my $array = shift;
  483. my $code = shift;
  484. return $array->map(
  485. bless sub {
  486. return $_[0] if $code->($_[0]);
  487. return
  488. }, '_MySub' );
  489. }
  490. sub kv {
  491. my $array = shift;
  492. my $count = 0;
  493. return $array->map(
  494. sub {
  495. return ( $count++, $_[0] )
  496. } );
  497. }
  498. sub keys {
  499. my $array = shift;
  500. my $count = 0;
  501. return $array->map(
  502. sub {
  503. $count++
  504. } );
  505. }
  506. sub values {
  507. @_
  508. }
  509. =head1 NAME
  510. Pugs::Runtime::Value::List - Perl extension for Perl6 "List" class
  511. =head1 SYNOPSIS
  512. use Pugs::Runtime::Value::List;
  513. my $list = Pugs::Runtime::Value::List.from_range( start => 10, end => 20 );
  514. my $list = Pugs::Runtime::Value::List.new( ... );
  515. =head1 DESCRIPTION
  516. This module implements a "List" object.
  517. new() without parameters is an empty list.
  518. =head1 SEE ALSO
  519. Pugs
  520. =head1 AUTHOR
  521. Flavio S. Glock, E<lt>fglock@gmail.com<gt>
  522. =head1 COPYRIGHT AND LICENSE
  523. Copyright (C) 2005 by Flavio S. Glock
  524. This library is free software; you can redistribute it and/or modify
  525. it under the same terms as Perl itself, either Perl version 5.8.4 or,
  526. at your option, any later version of Perl 5 you may have available.
  527. =cut