Permalink
Browse files

PieceTable initial commit

  • Loading branch information...
1 parent d9720d7 commit 98beca0e47a9db590dc336b521e53d65ea50beb3 Hakim Cassimally committed May 20, 2011
@@ -0,0 +1,114 @@
+package Text::PieceTable;
+use Text::PieceTable::Source;
+use Text::PieceTable::Piece;
+use List::Util 'sum';
+
+use Data::Dumper;
+local $Data::Dumper::Indent = 1;
+
+use Moose;
+
+has source => (
+ isa => 'Text::PieceTable::Source',
+ is => 'ro',
+ default => sub {
+ Text::PieceTable::Source->new({
+ data => '',
+ })
+ },
+);
+
+has additional => (
+ isa => 'Text::PieceTable::Source',
+ is => 'ro',
+ default => sub {
+ Text::PieceTable::Source->new({
+ data => '',
+ })
+ },
+);
+has pieces => (
+ traits => ['Array'],
+ is => 'ro',
+ isa => 'ArrayRef[Text::PieceTable::Piece]',
+ default => sub { [] },
+ handles => {
+ all_pieces => 'elements',
+ find_piece => 'first',
+ map_pieces => 'map',
+ push_piece => 'push',
+ },
+);
+
+sub from_string {
+ my ($class, $string) = @_;
+
+ my $source = Text::PieceTable::Source->new({
+ data => $string,
+ });
+ my $self = $class->new(source => $source);
+ if (length $string) {
+ $self->push_piece($source->whole_piece);
+ }
+ return $self;
+}
+
+sub piece_at_pos {
+ my ($self, $pos) = @_;
+
+ my $i = 0;
+ my @before;
+ my @pieces = $self->all_pieces;
+
+ while (my $piece = shift @pieces) {
+ my $len = $piece->length;
+ if ($i + $len > $pos) {
+ return ($piece, $pos - $i, \@before, \@pieces);
+ }
+ if ($i + $len == $pos and !@pieces) {
+ return ($piece, $len, \@before, \@pieces);
+ }
+ push @before, $piece;
+ $i += $len;
+ }
+ die "Invalid pos $pos";
+}
+
+sub insert {
+ my ($self, $pos, $text) = @_;
+
+ my $piece = $self->additional->append($text);
+ return $self->insert_piece($pos, $piece);
+}
+
+sub insert_piece {
+ my ($self, $pos, $insert) = @_;
+ my ($piece, $i, $before, $after) =
+ $self->piece_at_pos($pos);
+
+ my ($pre,$post) = $piece->split_at($i);
+
+ my $pieces = [
+ @$before,
+ $pre ? $pre : (),
+ $insert,
+ $post ? $post : (),
+ @$after,
+ ];
+ return $self->new(
+ source => $self->source,
+ additional => $self->additional,
+ pieces => $pieces,
+ );
+}
+
+sub as_string {
+ my $self = shift;
+ return join '' => $self->map_pieces( sub { $_->as_string } );
+}
+sub length {
+ my $self = shift;
+ return sum $self->map_pieces( sub { $_->length } );
+}
+
+no Moose; 1;
@@ -0,0 +1,52 @@
+package Text::PieceTable::Piece;
+
+use Moose;
+
+has source => (
+ isa => 'Text::PieceTable::Source',
+ is => 'ro',
+);
+
+has from => (
+ isa => 'Int',
+ is => 'ro',
+);
+
+has length => (
+ isa => 'Int',
+ is => 'ro',
+);
+
+sub as_string {
+ my $self = shift;
+ return $self->source->substr($self->from, $self->length);
+}
+
+sub split_at {
+ my ($self, $pos) = @_;
+ if ($pos) {
+ if ($pos < $self->length) {
+ my $source = $self->source;
+ return (
+ Text::PieceTable::Piece->new(
+ source => $source,
+ from => $self->from,
+ length => $pos,
+ ),
+ Text::PieceTable::Piece->new(
+ source => $source,
+ from => $self->from + $pos,
+ length => $self->length - $pos,
+ )
+ );
+ }
+ else {
+ return ($self, undef);
+ }
+ }
+ else {
+ return (undef, $self);
+ }
+}
+
+no Moose; 1;
@@ -0,0 +1,38 @@
+package Text::PieceTable::Source;
+
+use Moose;
+
+has data => (
+ traits => ['String'],
+ isa => 'Str',
+ is => 'rw', # only appendable, so easily shared!
+ default => '',
+ handles => {
+ _append => 'append',
+ length => 'length',
+ substr => 'substr',
+ },
+);
+
+sub append {
+ my ($self, $string) = @_;
+ my $pos = $self->length;
+
+ $self->_append($string);
+ return Text::PieceTable::Piece->new(
+ source => $self,
+ from => $pos,
+ length => length $string,
+ );
+}
+
+sub whole_piece {
+ my $self = shift;
+ return Text::PieceTable::Piece->new(
+ source => $self,
+ from => 0,
+ length => $self->length,
+ );
+}
+
+no Moose; 1;
@@ -0,0 +1,20 @@
+use Test::More;
+use Data::Dumper;
+local $Data::Dumper::Indent = 1;
+
+use Text::PieceTable;
+my $pt = Text::PieceTable->from_string('fox');
+is $pt->as_string, 'fox';
+
+$pt = $pt->insert(0, 'the quick ');
+is $pt->as_string, 'the quick fox';
+
+
+# $pt = $pt->insert(-1, ' jumps over the lazy dog');
+$pt = $pt->insert($pt->length, ' jumps over the lazy dog');
+is $pt->as_string, 'the quick fox jumps over the lazy dog';
+
+$pt = $pt->insert(10, 'brown ');
+is $pt->as_string, 'the quick brown fox jumps over the lazy dog';
+
+done_testing;

0 comments on commit 98beca0

Please sign in to comment.