Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw

A Tricky Problem with Context

by tlhf (Scribe)
on Apr 07, 2002 at 19:17 UTC ( #157300=perlquestion: print w/replies, xml ) Need Help??

tlhf has asked for the wisdom of the Perl Monks concerning the following question:

Recently, I've been writing a very high level VBScript to Perl compiler, or a 'translator'. By recently, I mean over the last year or so, with odd bits of hacking on it when I felt like it. Most of the problems I've faced have fallen, but there is one I've been putting off till now, because it scared me.

Let's look at the VBScript code;
option explicit dim x() x = split("john,biran,someone",",") response.write(x(0) & x(2))
The parser sees the option explicit, and realises that that means use strict. It sees the dim x(), and realises that that means my(@x). It understands that x is an array, and the parser always treats a solitry x as @x. And it goes forth, so the translation comes out roughly as;
use strict; my (@x); @x = b_split('john,biran,someone',','); $response->write(@x[0] . @x[2]); sub b_split { # not fully finished, needs $count and $compare my ($string, $delimiter, $count, $compare) = @_; $delimiter = quotemeta($delimiter); return split(/$delimiter/, $string); }
The @x[0] isn't clean, but it will do for now. This code works fine, and all is well. Well; all is well when we have wonderous option explicit. Unfortunately, there are many naughty VBScript developers who don't use option explicit. They don't define their variables. And my module gets very confused.

Without the option explicit and the dim x(), my translator outputs:
$x = b_split('john,biran,someone',','); print($x[0]; . $x[2]); sub b_split { # not fully finished, needs $count and $compare my ($string, $delimiter, $count, $compare) = @_; $delimiter = quotemeta($delimiter); return split(/$delimiter/, $string); }
Which is all kinds of wrong. So, I started my quest for the solution.

My first thought was to try to figure out what x would be by looking forward. Only that's a silly thought, because x could be redefined dynamically, to maybe a string or an integer rather than an array.

My second thought was to try to work out what it would be via a perl function. Something like C<context("x") = b_split('john,biran,someone',',');>. But this did not work either, for I knew now way of acheiving such a feat. Until someone on IRC pointed me towards the wonderus lvalue. Yet even after playing with it, lvalue reaped no rewards. It seemed to have little provision for the arrays and lists, and the context() seemed to execute before the lvalue passing occured.

I was going too play with some of the deepest perl magics I know of, the illustrius tie, when I ran accross this url. It seems to achieve what I want to do, yet I know not of how to use it. And I'm not sure if it does achieve anything similar to what I want to do. I feel like a lost little boy.

Has anyone got any ideas to help me solve my problem?

Replies are listed 'Best First'.
Re: A Tricky Problem with Context
by stephen (Priest) on Apr 07, 2002 at 21:32 UTC

    Without knowing enough about VB, it's kind of difficult to solve the problem, but here are some pointers and suggestions...

    For general cases, you could refer to all variables through objects. It might make it easier for your compiler to refer to variables, since they'd be accessable in a more VBScript-type way. It would be be a little less efficient, but would give you a way of dealing with the fact that the thing named 'x' could be a list, a hash, or a scalar. Like so:

    use VBVar; use strict; # We have b_split return a VBVar object my $x = b_split('john,biran,someone',','); # Use value_of(), with optional arguments, to return # the value of the variable $response->write( $x->value_of(0), $x->value_of(2)); sub b_split { # not fully finished, needs $count and $compare my ($string, $delimiter, $count, $compare) = @_; $delimiter = quotemeta($delimiter); # Passes an array ref to the VBVar constructor, # which then creates the underlying object of the right # kind return VBVar->new([ split(/$delimiter/, $string) ]); }
    And in
    package VBVar; use strict; our %VarTypes = ( ARRAY => 'VBVar::Array', SCALAR => 'VBVar::Scalar', HASH => 'VBVar::Hash' ); ## Have our child classes refer to VBVar as needed @VBVar::Array::ISA = ('VBVar'); @VBVar::Scalar::ISA = ('VBVar'); @VBVar::Hash::ISA = ('VBVar'); ## ## Blesses the reference we pass in to the appropriate ## VBVar type. ## sub new { my $type = shift; my $self = shift; bless $self, $VarTypes{ref $self}; } ## Return the value of VBVar if contents are an array sub VBVar::Array::value_of { my $self = shift; my @indices = @_; # If someone passed in a list of indices to return if (@indices) { return $self->[@indices]; } # Otherwise, return the whole array else { return @$self; } } ## Return the value of VBVar if contents are ## a scalar sub VBVar::Scalar::value_of { return ${ $_[0] }; }

    N.B. Code not tested, may not even compile, and should not be used directly; just trying to get the idea across.

    The advantage to this is that you can have the function that returns the value decide what kind of variable it is. You could also do the same thing by just returning references all the time, and have all of your functions check for type with 'ref' and take whatever action is appropriate.

    For information on tie, see perltie.


Re: A Tricky Problem with Context
by ilcylic (Scribe) on Apr 08, 2002 at 02:42 UTC
    I'm not sure how well this would work, but it seems as though you could implement some sort of "multi-pass compiler" type solution.

    By that I mean, have your script go through the VB script a couple of times. The first pass does what (I think) stephen is suggesting, having your script try and determine what each variable type is on any given line (or, per statement, really) and returning the reply in some sort of unique markup language that is internal to your compiler. The second pass goes through and compares the VBscript to your newly generated first pass document, and makes sure the actual sigils $, @, %, etc match what the vb script is actually doing at that point. (In fact, it might be a good idea to number each statement of the vb script as you go through it, and put the corresponding number at the beginning of the perl script line, to make sure that the compiler can determine which statements are supposed to correlate.) The third pass would go through the final perl script and clean up all of your internal stuff.

    I'm certainly no expert on writing compilers/translators, so don't take my suggestion as gospel, but it sounds like it could work.

    -il cylic
Re: A Tricky Problem with Context
by RSevrinsky (Scribe) on Apr 08, 2002 at 08:37 UTC
    I don't think that a second parse or using tie would make any difference here. Bear in mind that Visual Basic is not that smart either. I also thought that an unexplicitly-defined variable, like x in your example, is treated by VB as the omnivorous Variant, but MSDN claims that it automatically becomes an object. Strange -- that might be a difference between VB6 and VB.NET.

    You are clearly building your own namespace table -- that's how you determined in the first example that x was an array. In nonstrict code, you're just going to have to determine a variable's type based on assignment. In the second example, that's not too difficult: split would imply an array. But what if the unknown Object is returned from a function:

    x = FetchSomethingStrange(a) Function FetchSomethingStrange(p as Integer) If p < 5 Then FetchSomethingStrange = split("my,array,result", ",") Else FetchSomethingStrange = "my string result" End If End Function

    Now, in this case, there's no way of knowing until runtime whether FetchSomethingStrange is going to return a String or an array. That leaves you with 2 options (as I see it):

    • a VBVar object, like stephen suggested
    • translating all VB arrays to Perl listrefs.

    The listrefs would translate your nonstrict code as follows:

    $x = b_split('john,biran,someone',','); print($x->[0] . $x->[2]); sub b_split { # not fully finished, needs $count and $compare my ($string, $delimiter, $count, $compare) = @_; $delimiter = quotemeta($delimiter); return [ split(/$delimiter/, $string) ]; }

    My code translates well, too:

    $x = FetchSomethingStrange($a); sub FetchSomethingStrange { my($p) = @_; my $FetchSomethingStrange; # Autogenerated return value for VB funct +ions if ($p < 5) { $FetchSomethingStrange = b_split("my,array,result", ","); } else { $FetchSomethingStrange = "my string result" } return $FetchSomethingStrange; # Autoreturn }

    Again, you should be fine as long as the Perl translation is always talking in scalars, be they adapted VB scalars (String, Integer, Double) , Variant or Object types, or listrefs derived from Variant/Object auto-arrays or even from explicitly Dimed arrays.

    - Richie

      * translating all VB arrays to Perl listrefs.



      Wow, I've done some testing with that, and I seem to be able to get some of it working :-). I'm still unsure of whether to go with the 'VBVar' idea or the listref idea - the listref seems faster, but VBVar seems like it would provide more compatibility.

      btw, my converter is for VBScript, not VB. Think ASP.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://157300]
Approved by Dog and Pony
Front-paged by RMGir
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (4)
As of 2023-03-28 12:01 GMT
Find Nodes?
    Voting Booth?
    Which type of climate do you prefer to live in?

    Results (67 votes). Check out past polls.