ljr/wcmtools/s2/S2/NodeVarRef.pm

302 lines
8.3 KiB
Perl
Executable File

#!/usr/bin/perl
#
package S2::NodeVarRef;
use strict;
use S2::Node;
use S2::NodeExpr;
use S2::Type;
use vars qw($VERSION @ISA $LOCAL $OBJECT $PROPERTY);
$LOCAL = 1;
$OBJECT = 2;
$PROPERTY = 3;
$VERSION = '1.0';
@ISA = qw(S2::Node);
sub new {
my ($class) = @_;
my $n = new S2::Node;
bless $n, $class;
}
sub canStart {
my ($class, $toker) = @_;
return $toker->peek() == $S2::TokenPunct::DOLLAR;
}
sub parse {
my ($class, $toker) = @_;
my $n = new S2::NodeVarRef();
$n->{'levels'} = [];
$n->{'type'} = $LOCAL;
# voo-doo so tokenizer won't continue parsing a string
# if we're in a string and trying to parse interesting things
# involved in a VarRef:
$n->setStart($n->requireToken($toker, $S2::TokenPunct::DOLLAR, 0));
$toker->pushInString(0); # pretend we're not, even if we are.
if ($toker->peekChar() eq "{") {
$n->requireToken($toker, $S2::TokenPunct::LBRACE, 0);
$n->{'braced'} = 1;
} else {
$n->{'braced'} = 0;
}
if ($toker->peekChar() eq ".") {
$n->requireToken($toker, $S2::TokenPunct::DOT, 0);
$n->{'type'} = $OBJECT;
} elsif ($toker->peekChar() eq "*") {
$n->requireToken($toker, $S2::TokenPunct::MULT, 0);
$n->{'type'} = $PROPERTY;
}
my $requireDot = 0;
# only peeking at characters, not tokens, otherwise
# we could force tokens could be created in the wrong
# context.
while ($toker->peekChar() =~ /[a-zA-Z\_\.]/)
{
if ($requireDot) {
$n->requireToken($toker, $S2::TokenPunct::DOT, 0);
} else {
$requireDot = 1;
}
my $ident = $n->getIdent($toker, 1, 0);
my $vl = {
'var' => $ident->getIdent(),
'derefs' => [],
};
# more preventing of token peeking:
while ($toker->peekChar() eq '[' ||
$toker->peekChar() eq '{')
{
my $dr = {}; # Deref, 'type', 'expr'
my $t = $n->eatToken($toker, 0);
if ($t == $S2::TokenPunct::LBRACK) {
$dr->{'type'} = '[';
$n->addNode($dr->{'expr'} = S2::NodeExpr->parse($toker));
$n->requireToken($toker, $S2::TokenPunct::RBRACK, 0);
} elsif ($t == $S2::TokenPunct::LBRACE) {
$dr->{'type'} = '{';
$n->addNode($dr->{'expr'} = S2::NodeExpr->parse($toker));
$n->requireToken($toker, $S2::TokenPunct::RBRACE, 0);
} else {
die;
}
push @{$vl->{'derefs'}}, $dr;
}
push @{$n->{'levels'}}, $vl;
} # end while
# did we parse just $ ?
S2::error($n, "Malformed variable reference") unless
@{$n->{'levels'}};
if ($n->{'braced'}) {
# false argument necessary to prevent peeking at token
# stream while it's in the interpolated variable parsing state,
# else the string text following the variable would be
# treated as if it were outside the string.
$n->requireToken($toker, $S2::TokenPunct::RBRACE, 0);
}
$toker->popInString(); # back to being in a string if we were
# now we must skip white space that requireToken above would've
# done had we not told it not to, but not if the main tokenizer
# is in a quoted string
if ($toker->{'inString'} == 0) {
$n->skipWhite($toker);
}
return $n;
}
# if told by NodeTerm.java, add another varlevel to point to
# this object's $.as_string
sub useAsString {
my $this = shift;
push @{$this->{'levels'}}, {
'var' => 'as_string',
'derefs' => [],
};
}
sub isHashElement {
my $this = 0;
return 0 unless @{$this->{'levels'}};
my $l = $this->{'levels'}->[-1];
return 0 unless @$l;
my $d = $l->[-1];
return $d->{'type'} eq "{";
}
sub getType {
my ($this, $ck, $wanted) = @_;
if (defined $wanted) {
my $t = getType($this, $ck);
return $t unless
$wanted->equals($S2::Type::STRING);
my $type = $t->toString();
if ($ck->classHasAsString($type)) {
$this->{'useAsString'} = 1;
return $S2::Type::STRING;
}
}
# must have at least reference something.
return undef unless @{$this->{'levels'}};
my @levs = @{$this->{'levels'}};
my $lev = shift @levs; # VarLevel
my $vart = undef; # Type
# properties
if ($this->{'type'} == $PROPERTY) {
$vart = $ck->propertyType($lev->{'var'});
S2::error($this, "Unknown property") unless $vart;
$vart = $vart->clone();
}
# local variables.
if ($this->{'type'} == $LOCAL) {
$vart = $ck->localType($lev->{'var'});
S2::error($this, "Unknown local variable \$$lev->{'var'}") unless $vart;
}
# properties & locals
if ($this->{'type'} == $PROPERTY ||
$this->{'type'} == $LOCAL)
{
$vart = $vart->clone();
# dereference [] and {} stuff
$this->doDerefs($ck, $lev->{'derefs'}, $vart);
# if no more levels, return now. otherwise deferencing
# happens below.
return $vart unless @levs;
$lev = shift @levs;
}
# initialize the name of the current object
if ($this->{'type'} == $OBJECT) {
my $curclass = $ck->getCurrentFunctionClass();
S2::error($this, "Can't reference member variable in non-class function") unless $curclass;
$vart = new S2::Type($curclass);
}
while ($lev) {
my $nc = $ck->getClass($vart->toString());
S2::error($this, "Can't use members of an undefined class") unless $nc;
$vart = $nc->getMemberType($lev->{'var'});
S2::error($this, "Can't find member '$lev->{'var'}' in " . $nc->getName()) unless $vart;
$vart = $vart->clone();
# dereference [] and {} stuff
$this->doDerefs($ck, $lev->{'derefs'}, $vart);
$lev = shift @levs;
}
return $vart;
}
# private
sub doDerefs {
my ($this, $ck, $derefs, $vart) = @_;
foreach my $d (@{$derefs}) {
my $et = $d->{'expr'}->getType($ck);
if ($d->{'type'} eq "{") {
S2::error($this, "Can't dereference a non-hash as a hash")
unless $vart->isHashOf();
S2::error($this, "Must dereference a hash with a string or int")
unless ($et->equals($S2::Type::STRING) ||
$et->equals($S2::Type::INT));
$vart->removeMod(); # not a hash anymore
} elsif ($d->{'type'} eq "[") {
S2::error($this, "Can't dereference a non-array as an array ")
unless $vart->isArrayOf();
S2::error($this, "Must dereference an array with an int")
unless $et->equals($S2::Type::INT);
$vart->removeMod(); # not an array anymore
}
}
}
# is this variable $super ?
sub isSuper {
my ($this) = @_;
return 0 if $this->{'type'} != $LOCAL;
return 0 if @{$this->{'levels'}} > 1;
my $v = $this->{'levels'}->[0];
return ($v->{'var'} eq "super" &&
@{$v->{'derefs'}} == 0);
}
sub asS2 {
my ($this, $o) = @_;
die "Unported";
}
sub asPerl {
my ($this, $bp, $o) = @_;
my $first = 1;
if ($this->{'type'} == $LOCAL) {
$o->write("\$");
} elsif ($this->{'type'} == $OBJECT) {
$o->write("\$this");
} elsif ($this->{'type'} == $PROPERTY) {
$o->write("\$_ctx->[PROPS]");
$first = 0;
}
foreach my $lev (@{$this->{'levels'}}) {
if (! $first || $this->{'type'} == $OBJECT) {
$o->write("->{'$lev->{'var'}'}");
} else {
my $v = $lev->{'var'};
if ($first && $this->{'type'} == $LOCAL &&
$v eq "super") {
$v = "this";
}
$o->write($v);
$first = 0;
}
foreach my $d (@{$lev->{'derefs'}}) {
$o->write("->$d->{'type'}"); # [ or {
$d->{'expr'}->asPerl($bp, $o);
$o->write($d->{'type'} eq "[" ? "]" : "}");
}
} # end levels
if ($this->{'useAsString'}) {
$o->write("->{'as_string'}");
}
}
sub isProperty {
my $this = shift;
return $this->{'type'} == $PROPERTY;
}
sub propName {
my $this = shift;
return "" unless $this->{'type'} == $PROPERTY;
return $this->{'levels'}->[0]->{'var'};
}