package App::Asciio ; $|++ ; use strict; use warnings; use Data::TreeDumper ; #~ use Compress::LZF ':compress'; use Compress::Bzip2 qw(:all :utilities :gzip); #----------------------------------------------------------------------------- sub pop_undo_buffer { my ($self, $number_of_steps) = @_; pop @{$self->{DO_STACK}} for(1 .. $number_of_steps) ; } #----------------------------------------------------------------------------- sub redo { my ($self, $number_of_steps) = @_; $self->{DO_STACK_POINTER} += $number_of_steps ; if($self->{DO_STACK_POINTER} >= @{$self->{DO_STACK}}) { $self->{DO_STACK_POINTER} = @{$self->{DO_STACK}} - 1 ; } $self->do($self->{DO_STACK_POINTER}) ; } sub undo { my ($self, $number_of_steps) = @_; (my $new_stack_pointer = $self->{DO_STACK_POINTER}) -= $number_of_steps ; $new_stack_pointer = 0 if($new_stack_pointer < 0) ; $self->{DO_STACK} ||= [] ; if($self->{DO_STACK_POINTER} == @{$self->{DO_STACK}}) { $self->create_undo_snapshot() ; } $self->{DO_STACK_POINTER} = $new_stack_pointer ; $self->do($new_stack_pointer) ; } sub do { my ($self, $stack_pointer) = @_; my $new_self = $self->{DO_STACK}[$stack_pointer] ; if(defined $new_self) { my ($do_stack_pointer, $do_stack) = ($self->{DO_STACK_POINTER}, $self->{DO_STACK}) ; my $decompressed_new_self = decompress $new_self ; $decompressed_new_self .= "\n\n" ; # important line or eval would complains about syntax errors !!! my $VAR1 ; eval $decompressed_new_self ; if($@) { use File::Slurp ; write_file('undo_error.pl', $decompressed_new_self ) ; die "Can't undo! $@\n" ; } else { $self->load_self($VAR1) ; ($self->{DO_STACK_POINTER}, $self->{DO_STACK}) = ($do_stack_pointer, $do_stack) ; $self->update_display() ; } } } #----------------------------------------------------------------------------- sub create_undo_snapshot { my ($self) = @_; #TODO: delta, serialize and compress, use the same huffman table for extra compression my $serialized_self ; { local $self->{DO_STACK} = undef ; $serialized_self = $self->serialize_self() ; } #~ my $previous_serialized_self = '' ; #~ { #~ local $self->{DO_STACK} = undef ; #~ my $xxx= $self->serialize_self(1) ; #~ use File::Slurp ; #~ write_file("test/undo_$self->{DO_STACK_POINTER}.txt", $xxx) ; # diff serialize 1 + bzip 2 => 500-1000 bytes vs 4000-5000 bytes with no diff and compress #~ } my $compressed_self = compress $serialized_self ; splice(@{$self->{DO_STACK}}, min($self->{DO_STACK_POINTER}, scalar(@{$self->{DO_STACK}}))) ; # new do branch push @{$self->{DO_STACK}}, $compressed_self ; $self->{DO_STACK_POINTER} = @{$self->{DO_STACK}} ; #~ print 'stack: ' . scalar(@{$self->{DO_STACK}}) . ' size: ' . length($serialized_self) . ' compressed: ' . length($compressed_self) . "\n" ; } #----------------------------------------------------------------------------- use Algorithm::Diff qw(diff LCS traverse_sequences) ; sub test_diff { # This example produces traditional 'diff' output: my @seq1 = ("line 1", "line 2", "line3", "line 4", "line 5", 'line 6') ; my @seq2 = ("line mod1", "line 2", "line 2B", "line3", "line 5") ; my @diff_lines = get_diff_lines(\@seq1, \@seq2) ; for my $diff_line (@diff_lines) { print DumpTree $diff_line ; my ( $number_of_errors , $number_of_match , $synchronized_a , $synchronized_b , $error_string ) = CompareStrings($diff_line->{REFERENCE}, $diff_line->{NEW}) ; my $undefined_line = '' ; $undefined_line = '** reference line did not exist! **' unless defined $diff_line->{REFERENCE} ; $undefined_line = '** new line did not exist! **' unless defined $diff_line->{NEW} ; print <{LINE} number_of_match = $number_of_match number_of_errors = $number_of_errors $undefined_line $synchronized_a $synchronized_b $error_string ERRORS } } sub get_diff_lines { my ($seq1, $seq2) = @_ ; my $diff = Algorithm::Diff->new($seq1, $seq2 ); my @diff_lines ; $diff->Base(1); my $line = 1 ; while($diff->Next()) { unless($diff->Same()) { my ($reference_line) = $diff->Items(1) ; my ($new_line) = $diff->Items(2) ; push @diff_lines, {LINE => $line, REFERENCE => $reference_line , NEW => $new_line} ; } $line++ ; } return @diff_lines ; } sub CompareStrings($$) { =head2 CompareStrings Returns the following list: =over 2 =item 1 number_of_errors =item 2 number_of_match =item 3 synchronized_a =item 4 synchronized_b =item 5 error_string =back =cut my ($a_string, $b_string) = @_ ; my ($a, $b) ; # handle cases were one or both strings are not defined if(!defined $a_string && ! defined $b_string) { return (0, 0, '', '', '') ; } elsif(!defined $a_string) { return (length($b_string), 0, '', $b_string, '^' x length($b_string)) ; } elsif(!defined $b_string) { return (length($a_string), 0, $a_string, '', '^' x length($a_string)) ; } my @a = split //, $a_string ; my @b = split //, $b_string ; my @match_indexes = Algorithm::Diff::_longestCommonSubsequence( \@a, \@b) ; #print Dumper(\@match_indexes), "\n" ; #my @LCS = LCS( \@a, \@b ) ; #print Dumper(\@LCS), "\n" ; my $previous = -1 ; my $last_match_in_B = -1 ; # build $a a character at a time. Synchronize strings before adding current character for(0 .. $#match_indexes) { if(defined $previous) { if(defined $match_indexes[$_]) { if($match_indexes[$_] == $previous + 1) { # match $b .= $b[$match_indexes[$_]] ; $last_match_in_B = $match_indexes[$_] ; } else { # match but extra letters in B, synchronize A $a .= ' ' x ($match_indexes[$_] - ($previous + 1)) ; $b .= join '', @b[$previous + 1 .. $match_indexes[$_]] ; $last_match_in_B = $match_indexes[$_] ; } } #else # letter in A doesn't match in B } else { if(defined $match_indexes[$_]) { # match # synchronize B my $number_of_skipped_characters_in_B = ($match_indexes[$_] - 1) - ($last_match_in_B) ; $b .= ' ' x (length($a) - (length($b) + $number_of_skipped_characters_in_B)) ; $b .= join '', @b[$last_match_in_B + 1 .. $match_indexes[$_]] ; $last_match_in_B = $match_indexes[$_] ; # synchronize A if needed $a .= ' ' x (length($b) - (length($a) + 1)) ; # +1 as current character will be appended to $a } #else # letter in A doesn't match in B } $a .= $a[$_] ; $previous = $match_indexes[$_] ; } my $trailers_in_A = scalar(@a) - scalar(@match_indexes) ; $a .= join '', @a[-$trailers_in_A .. -1] ; my $trailers_in_B = scalar(@b) - ($last_match_in_B + 1) ; $b .= join '', @b[-$trailers_in_B .. -1] ; my $error_string = $a ^ $b ; my $number_of_matches = $error_string =~ tr[\0][\0] ; my $number_of_errors = length($error_string) - $number_of_matches ; # show were the strings are different $error_string =~ tr[\0][^]c ; $error_string =~ tr[\0][ ] ; return ($number_of_errors, $number_of_matches, $a, $b, $error_string) ; } #----------------------------------------------------------------------------- 1 ;