http://qs1969.pair.com?node_id=283697

Hello, Good Monks,

I find myself wanting to get advice on some preliminary code that employs the techniques described in Filter::Simple's documentation. Although the documentation is probably very good, I am finding myself having trouble mentally parsing it, and so I think I am probably working harder / dumber than I ought to be.

The goal here is to provide a devel support framework or "scaffolding" for writing the kind of Perl code that doesn't look like Perl code to the stand-alone perl parser, unaided: Vim scripts *.vim employing the embedded perl interpreter that can be compiled (as an option) into the Vim editor. An example fragment of such a script appears below.


""; use VimFilter; func! VerDataFmt() perl <<EOSCRIPT #NOVIM# use strict; #NOVIM# use EasyOpen; #NOVIM# my @Dummy = @{EasyOpen::SlurpIn ('/tmp/gvim-ver-output.tmp')}; my($regQ,$VregS) = VIM::Eval('@v'); my($reqR,$IreqR) = VIM::Eval('columns'); $IreqR =~/\d{2,3}/ || ($IreqR = 96); if($regQ) { my $ln = my $mlen = 0; my(@plus,@minu,@cummup,@cummum); my @lwise = split "\n",$VregS; VIM::Msg('There are '.@lwise." lines in \@v\n"); foreach my $lines (@lwise) { next unless $ln++>=2; if($lines =~/^(?:\-|\+)\w+/) { my @bopts = split /(?<=\w)\b/, $lines; VIM::Msg("Ele in picked-out: ".@bopts."\nin line:$lines"); push @plus, grep{ s/^\+// and ($mlen=$mlen<length($_) ? length($_):$mlen +) }@bopts; push @minu, grep{ s/^\-// and ($mlen=$mlen<length($_) ? length($_):$mlen +) }@bopts; } last if $lines =~/^\s+system/; } VIM::Msg("The size of arrays \@plus and \@minu is " . @plus ." , ". @minu ."\n"); $curbuf->Set(0, "Features compiled into this Vim:") ; my $slotspln = sprintf '%u', ($IreqR / (2+$mlen)) - 0.5; my $formatex = "%$mlen-s "x$slotspln; my$PY = my$YP = 0; while ( $PY < @plus ) { local $^W = 0; push @cummup, sprintf($formatex, @plus[$PY .. ($PY+=($slotspln - 1))]); } continue { $PY++; } while ( $YP < @minu ) { local $^W = 0; push @cummum, sprintf($formatex, @minu[$YP .. ($YP+=($slotspln - 1))]); } continue { $YP++; } $curbuf->Append(1,@cummup,''); $curbuf->Append(2+@cummup,'Features omitted from this Vim:'); $curbuf->Append(3+@cummup,@cummum,''); } else { VIM::Msg('The contents of register v could not be accessed'); } __DATA__ EOSCRIPT endfu

Note about code above:

The VimFilter.pm module and the EasyOpen module are my inventions, the former is shown below and the latter is just a trivial little piece of code that's not worth showing here.

Why?

Those unfamiliar with the experience of writing Perl script as part of Vim subroutines (functions) won't necessarily know, but there's a challenge here that is different from that encountered in writing ordinary Perl programs. In an average Perl script the system perl interpreter is invoked directly and except in the case of GUI programs, has access to STDOUT and STDERR for purposes of informing the user of what might have gone wrong during some stage of parsing, compilation or execution.

In the case of the perl interpreter embedded into GUI Vim (GVIM), there is typically no output at all if the interpreter encounters a syntax error. Program execution simply terminates silently in such a case, offering not a clue about what might have gone wrong. As can be seen, this can make the write-run-diagnose, write-run-diagnose cycle much more laborious if GVIM is the only environment the script can be tested in during development. Essentially it's like having to shoot an arrow into the bull's eye each time, but if the bull's eye is missed there's no peripheral targets zones that count as "partial points" for hitting in. In my opinion this factor alone has made developing Vim scripts using Perl sufficiently hard so that not that many exist.

As a side note it should be recognized that Vim has it's own internal scripting language, and it's not too bad at all, actually pretty capable, but in my experience rather difficult to learn. I already know perl and would much rather program using perl than vim (or "ex" as Vim's command language is called).

The thing I found I wanted to do was just to be able to concentrate on write-test-write 'ing the script using the normal stand-alone perl to execute, instead of the one inside Vim. Then when the syntax of the perl part of the work checks out as sound, I run it as a Vim function by source'ing the script and :call'ing the function.

Finally, we get to the work that supports developing a perl-Vim function this way. The VimFilter.pm module gets used only if we are running in the ordinary perl. This module transforms the source code of the Vim scriptlet so that it will parse under the ordinary (non-embedded) perl interpreter. This module is what I ask advice on from the Monks. First of all, I'd like to be able to have more than one perl program in a single Vim script. Secondly, I am opening the file manually because I am not sure I can get the @Dummy text extracted later. Thirdly, I have an awkward mess of regexen going on in the sub{ paramater.

The @Dummy is a crutch required because inside the embedded interpreter environment, the $curbuf and other objects are automatically provided, whereas they are not present outside it, of course. The price paid by the scripter using this my system, is that they'll need to provide a simulation in static form of the dynamic object, so that their code can really be tested outside Vim. At the very least this array must be defined, even if empty, to prevent syntax errors under use strict;.

VimFilter.pm

package VimFilter; use FindBin qw($RealBin $RealScript); use Carp; BEGIN { $|++; #-------------------------------------------------------------------- # some code d/l from Perl Monks node: 106194 # View Original: http://www.perlmonks.org/?node=106194&displaytype=dis +playcode #-------------------------------------------------------------------- use Term::Cap; use POSIX; my $termios = new POSIX::Termios; $termios->getattr; my $ospeed = $termios->getospeed; my $t = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed }; ($NO, $BO, $US, $SO) = map { $t->Tputs($_,1) } qw/me md us so/; # ended borrowed code. $BO = $US; our ($Supplied_dummy, $VFD); open US , "< $RealBin/$RealScript" or croak "Cannot open() \"$RealScript\" in \"$RealBin\", $!"; local $/; undef $/; my $whole = <US>; close US or die "Went wrong close() ing, $!"; print STDERR "Magnitude scalar of file is ",length $whole, "\n"; if( $whole =~m/^(?:#NOVIM#)?\s*my\s*\(?\s*\@(\w*Dummy\w*)\s*\)?\s*=. ++?(?=;\n)/msi ) { $Supplied_dummy = $&; $VFD = $1; $Supplied_dummy =~s/^#NOVIM#//; } else { croak "\nCouldn't find a ${BO}\@Dummy${NO} declaration in your f +ile:\n", "${RealBin}/${RealScript}\n"; } print STDERR "We found your ${BO}\$Dummy${NO} declaration:", <<EOBLK + ; ---------------------------------------------------------------------- +----- ${SO}$Supplied_dummy${NO} ; Symbol name of dummy array: ${BO}$VFD${NO} ---------------------------------------------------------------------- +----- EOBLK } my $dummy = "${Supplied_dummy};\n"; my $dumEv = 'my $dummyEval = sub { (0,0) };' ; # prevent Perl from seeing Vim wrapper tokens, so that we can run/deb +ug outside Vim. use Filter::Simple sub { if(!eval 'VIM::Buffers > 0x0;') { s'#NOVIM#''g; s'\A\s*fun?c?t?i?o?n?!?\s+.*''m ; s[^\s*\Qperl <<\E.*] [$dumEv]m ; s'VIM::Msg'print STDERR 'mg ; s'VIM::Eval\s*\('&$dummyEval('mg ; s/\$ curbuf->(?:G|S)et\( ([^\)]+) \)/\$${VFD}[$1] /xg ; s/\$ curbuf->Count\(\) /scalar(\@${VFD})/xg ; s'\$ curbuf-> \w+ \(\) 'qq/DUMMY/'exg ; } } ; 1; =pod =head1 SYNOPSIS use VimFilter; =head1 NOTES The entire source file is slurped in as one long string. The sub{} is +not applied line-wise (looping through each line) but to the whole file, once. Trying to define a token to "ignore from here down" as suggestion in t +he docs for Filter::Simple didn't work. I probably didn't understand something abo +ut how to do that. The biggest challenge is to provide some kind of dummy array for the b +uilt-in automatic $curbuf object; we need to tailor that simulation to each application +of this module. A real refinement would be to treat each substitution as a replaceable + parameter, perhaps read in from a .conf file. One can even imagine a repository f +or those subregexen because the possible permutations are infinite! Using an external file + would be the only rational approach. =cut

    Intrepid/Soren

-- 
use PerlMonk::Tye qw(:wisely);