/usr/local/CPAN/HTML-Merge/HTML/Merge/Compile.pm


#####################################
package HTML::Merge::Compile;
#####################################
BEGIN 
{
	eval 'use HTML::Merge::Ext;';
}
# Modules ########################### 

use strict qw(subs vars);
use vars qw($open %enders %printers %tokenizers $VERSION $DEBUG 
		$INTERNAL_DB $INTERNAL_DB_TYPE);
use Carp;
use Config;
use subs qw(quotemeta);

#####################################
$VERSION = '3.54';
#####################################
# Globals ###########################
$open = '\$R';
#my @non_flow = qw(VAR SQL ASSIGN SET PSET PGET PIC STATE INDEX CFG);
#@non_flow{@non_flow} = @non_flow;

my @printers = qw(VERSION VAR SQL GET PGET PVAR INDEX PIC STATE CFG INI LOGIN 
	AUTH DECIDE EMPTY DATE DAY MONTH YEAR DATEDIFF LASTDAY ADDDATE
	USER MERGE TEMPLATE TRANSFER DUMP NAME TAG COOKIE SOURCE
	DATE2UTC UTC2DATE ENV DATEF EVAL HOUR MINUTE SECOND);
@printers{@printers} = @printers;

#my @stringers = qw(IF SET PSET SETCFG);
#@stringers{@stringers} = @stringers;

my @tokenizers = qw();
@tokenizers{@tokenizers} = @tokenizers;

%enders = qw(END_IF IF END LOOP END_WHILE WHILE);

$INTERNAL_DB_TYPE='SQLite';

#####################################
# locate the template from the various paths
sub GetTemplateFromPath
{
	my ($template) = @_;

        my @input = ("$HTML::Merge::Ini::TEMPLATE_PATH/$template",
         	     "$HTML::Merge::Ini::MERGE_ABSOLUTE_PATH/public/template/$template");

        # let lets find the input
        foreach (@input)
        {
                if (-f)
                {
                        return $_;
                }
	}
	
	return "$HTML::Merge::Ini::TEMPLATE_PATH/$template";
}
#####################################
sub WantPrinter 
{
	my ($self, $tag, $dtag, $dline) = @_;

	my $ret = $self->WantTag($tag);
	return $ret if ($printers{$tag});
	my $line = $self->Line;
	$self->Die("$tag is not an output tag, perhaps you forgot to close a string in tag $dtag from line $dline? Output tags are " . join(", ", keys %printers));
}
#####################################
sub Translate 
{
	my ($self, $exp) = @_;
	my $result = "\\\\[=\\.]";
	my $i;
	my @fetch;
	my $tail;

	while ($exp =~ s/^(.*?)([QUELD])//i) 
	{
		my ($before, $token) = ($1, uc($2));
		$result .= quotemeta(quotemeta($before));

		if ($token eq 'U') 
		{
			$result .= '(.*?)';
			$i++;
			push(@fetch, "\$$i");
		} 
		elsif ($token eq 'L') 
		{
			$result .= '([A-Z])';
			$i++;
			push(@fetch, "\$$i");
		} 
		elsif ($token eq 'Q') 
		{
			$i++;
			$result .= "\\\\(['\"])(.*?)\\\\\\$i";
			$i++;
			push(@fetch, "\$$i");
		} 
		elsif ($token eq 'E') 
		{
			$result .= '(?:';
			$tail = ')?' . $tail;
		} 
		elsif ($token eq 'D') 
		{
			$result .= "\\\\[\\.=]";
		} 
		else 
		{
			$self->Die("Unknown notator: $token");
		}
	}

	$result .= quotemeta(quotemeta($exp)) . $tail;
	my $fetch = '(' . join(", ", @fetch) . ')';
	($result, $fetch);
}
#################################
# CGI parsing utility		#
#################################
sub ParseForm
{
        my $toParse = shift;
        my ($name , $value , @pairs , $pair , %FORM);
        @pairs = split(/&/, $toParse);
        foreach $pair (@pairs) {
                ($name, $value) = split(/=/, $pair);
                $value =~ tr/+/ /;
                $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
                $FORM{$name} = $value;
                #Debug("kak : $name \=  $value");
        }
        return \%FORM;
}
#####################################
sub CgiParse
{
        my $GFORM =  &ParseForm($ENV{'QUERY_STRING'});
        my $buffer;
        read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
        my $PFORM = &ParseForm($buffer);

        my (%FORM , $key);
        foreach $key(keys %$GFORM){
                $FORM{$key} = $GFORM->{$key};
        }

        foreach $key(keys %$PFORM){
                $FORM{$key} = $PFORM->{$key};
        }
        return \%FORM;
}
#####################################
sub WantTag 
{
	my ($self, $tag, $inv) = @_;
	my $candidate = $enders{$tag};
	if ($candidate && !$inv) 
	{
		$tag = $candidate;
		$inv = 1;
	}
	my $un = $inv ? "Un" : "";
	my $code = UNIVERSAL::can($self, "Do$un$tag");
	return $code if $code;
	my $macro = UNIVERSAL::can('HTML::Merge::Ext', "MACRO_$tag");
	if ($macro) 
	{
		my $proto = prototype("HTML::Merge::Ext::MACRO_$tag");
		my $text = quotemeta(&$macro);
		$proto = " ($proto)" if $proto;

		eval <<EOM;
		package HTML::Merge::Ext;

		sub API_$tag$proto 
		{
			Macro("$text", \@_);
		}
EOM
	}

	foreach my $api (qw(API OUT)) 
	{
		my $candidate = "RUN${api}_$tag";
		my $code = UNIVERSAL::can('HTML::Merge::Ext', $candidate);
		if ($code)
		{
			my $proto = prototype("HTML::Merge::Ext::$candidate");
			$proto =~ s/;.*$//;
			$self->Die("Prototype for $candidate may include only \$ signs")
			unless ($proto =~ /^\$*$/);
			my $check = "${api}_$tag";
			my $code = UNIVERSAL::can('HTML::Merge::Ext', $check);
			unless ($code) 
			{
				my @par;
				my $i = 0;
				foreach (split(//, $proto)) 
				{
					push(@par, qq{"\$_[$i]"});
					$i++;
				}
				my $pass = join(", ", @par);
				my $text = "package HTML::Merge::Ext;
										sub $check ($proto) 
										{
												$candidate($pass);
										}";
				eval $text;
				die $@ if $@;
				last;
			}
		}
	}
	my @options = !$inv ? qw(API OAPI OUT) : qw(CAPI);
	foreach my $api (@options) 
	{
		my $candidate = "${api}_$tag";
		$code = UNIVERSAL::can('HTML::Merge::Ext', $candidate);
		if ($code) 
		{
			my $ref = ref($self);
			my $proto = prototype("HTML::Merge::Ext::$candidate");
			$proto =~ s/;.*$//;
			$self->Die("Prototype for $candidate may include only \$ signs")
				unless ($proto =~ /^\$*$/);
			my $n = length($proto);
			my $shift = join(", ",
				map {"\$param[$_]";} (0 .. $n - 1));
			my $stack;
			my $scope = lc($tag);
			if ($api eq 'OAPI') 
			{
				$stack = qq!\$self->Push('$scope', \$engine);!;
			}
			if ($api eq 'CAPI') 
			{
				$stack = qq!\$self->Expect(\$engine, '$scope');!
			}
			my $desc = UNIVERSAL::can('HTML::Merge::Ext',
				"DESC_$tag");
			my $expand;
			unless ($desc) 
			{
				$expand = 'my @param = @$param;';
				$tokenizers{$tag} = 1;
			} 
			else 
			{
				if ($api eq 'CAPI') 
				{
					$expand = 'my @param;';
				} 
				else 
				{
					my $txt = &$desc;
					my ($re, $form) = $self->Translate($txt);
					$expand = <<EOM;
	unless (\$param =~ /^$re\$/s) 
	{
		\$self->Syntax;
	}
	my \@param = $form;
EOM
				}
			}
			my $extend = <<EOM;
package $ref;
sub Do$un$tag 
{
	my (\$self, \$engine, \$param) = \@_;
	$expand
	my \$n = \@param;
	\$self->Die("$n parameters expected for $tag, gotten \$n") unless (\$n == $n);
	$stack
	\$HTML::Merge::Ext::ENGINE = \$engine;
	\$HTML::Merge::Ext::COMPILER = \$self;
	HTML::Merge::Ext::$candidate($shift);
}
EOM
			eval $extend;
			$self->Die($@) if $@;
			$printers{$tag} = ($api eq 'OUT');
			return $self->WantTag($tag, $inv);
		}
	}
	$self->Die("$tag is not a valid Merge tag");
}
#####################################
sub quotemeta {
	my $text = CORE::quotemeta(shift);
	$text =~ s/\\ / /g;
	$text =~ s/\\\t/\t/g;
	$text;
}
#####################################
sub Compile {
	my $self = {'buffer' => '', 'scopes' => []};
	my $class = __PACKAGE__;
	my $in = $HTML::Merge::config;
	$in =~ s|/\w+\.\w+$||;
	$in =~ s|^/*||;
	$in =~ s/[\/\\]/::/g;
	$in =~ tr/A-Za-z0-9_://cd;
	if ($in) {
		my $code =  <<EOM;
package ${class}::$in;
use strict 'vars';
use vars qw(\@ISA);
\@ISA = qw($class);
EOM
		eval $code;
		die $@ if $@;
		$class .= "::$in";
	}
	bless $self, $class;
	$self->{'source'} = shift;
	$self->{'source'} =~ s/\r\n/\n/g;
	$self->{'save'} = $self->{'source'};
	$self->{'name'} = shift;
	$self->{'template'} = $self->{'name'};
	$self->{'template'} =~ s|^$HTML::Merge::Ini::TEMPLATE_PATH/||;
	$self->{'force line'} = shift;
	$self->Main;
	$self->{'buffer'};
}
#####################################
sub Clone {
	my $self = shift;

	return bless {},ref($self);
}
#####################################
sub Clause {
	my ($self,$text,$in) = @_;

	my $new=$self->Clone();
	my $error;
	my $res;

	$new->{'save'} = $new->{'source'} = "$text>";	
	eval{
		$res=$new->EatParam($in);
	};

	if($@){
		$error=$@;
		$error=~ s/ at .* line .*$//;
		$self->Die($error);		
	}
	$res=~ s/\n+$//s;

	return $res;
}
#####################################
sub Line {
	my $self = shift;
	my $force = $self->{'force line'};
	return $force if $force;
	my @lines = split(/\n/, $self->{'save'});
	my $left = substr($self->{'save'}, -length($self->{'source'}));
	my @ll = split(/\n/, $left);
	my $this = @lines - @ll + 1;
}
#####################################
sub Mark {
	my $self = shift;
	my $name = $self->{'name'};
	my $this = $self->Line;
	return unless $name;
	$self->{'buffer'} .= "\$HTML::Merge::context = [\"$name\", \"$this\"];\n";
	$self->{'buffer'} .= "#line $this $name\n";
	return;
}
#####################################
sub Die {
	my ($self, $error) = @_;
	my $this = $self->Line;
	my $s = (split(/\n/, $self->{'save'}))[$this - 1];
	my $name = $self->{'name'};
	if ($error < 0) {
		die "Depcrecated: Die(negative)";
	}

	$name =~ s|^.*/||;		
	Carp::cluck "Error: $error at $name line $this when doing: $s" if $DEBUG
		|| $ENV{'MERGE_DEBUG'};
	die "Error: $error at $name line $this, when doing: $s";
}
#####################################
sub Main {
	my $self = shift;
	$self->{'source'} =~ s/<(BODY)/<!-- GENERATOR: "Merge v. $VERSION (c) Raz Information systems www.raz.co.il" -->\n<$1/i;
	while  ($self->EatOne) {}
	$self->PrePrint($self->{'source'});
	$self->{'source'} = '';
	if (@{$self->{'scopes'}}) {
		my @scopes = map {join("/", @$_);} @{$self->{'scopes'}};
		my $stack = join(", ", @scopes);
		$self->Die("Stack not empty: $stack");
	}
}
#####################################
sub EatOne {
	my $self = shift;
	if ($self->{'source'} =~ s/^(.*?)\<(\/?)$open(\[.+?\]\.)?(\w+)//si) {
		my ($head, $close, $engine, $tag, $param) = ($1, $2, $3, uc($4));
		$engine =~ s/^\[(.*)\]\./$1/;
		$engine= $self->Clause($engine,$tag) if($engine=~ /\<$open/);
	
		my $code = $self->WantTag($tag, $close);
		$param = $self->EatParam($tag);
		$self->Die("Closing tags may not have parameters") if (($close || $enders{$tag}) && ($param && !ref($param) || ref($param) && $#$param >= 0));
		$self->Mark;
		if ($printers{$tag}) {
			$self->PrePrint($head);
			$self->{'buffer'} .= "print (";
		} else {
			$head =~ s/\s+$//s;
			$self->PrePrint($head);
		}
		$self->{'buffer'} .= &$code($self, $engine, $param);
		if ($printers{$tag}) {
			$self->{'buffer'} .= ");\n";
		}
		return 1;
	}
	undef;
}
#####################################
sub Macro {
	my ($self, $text) = @_;
	my $length = length($self->{'source'});
	my $lennow;

	$self->{'source'} = $text . $self->{'source'};
	for (;;) {
		$lennow = length($self->{'source'});
		last if ($lennow <= $length);
		my $left = $lennow - $length;
		last if $self->{'source'} =~ /^\s{$left}/;

		$self->EatOne || last;
	}
	my $remainder = $lennow - $length;
	$self->Die("macro did not resolve correctly") if ($remainder < 0);
	$self->PrePrint(substr($self->{'source'}, 0, $remainder));
	substr($self->{'source'}, 0, $remainder) = "";
}
#####################################
sub PrePrint {
	my ($self, $string) = @_;
	while ($string =~ s/^(.*?)\0(.*?)\0//) {
		my ($b4, $bt) = ($1, $2);

		$self->Print($b4);
		$self->{'buffer'} .= qq'print "$bt";';
	}
	$self->Print($string) if $string;
}
#####################################
sub Print {
	my ($self, $string) = @_;
	my @lines = split(/\n/, $string);
	my $last = pop @lines;
	foreach (@lines) {
		$self->{'buffer'} .= 'print "' . quotemeta($_) . '\n";' . "\n";
	}
	$self->{'buffer'} .= 'print "' . quotemeta($last) . '";' . "\n";
	$self->{'buffer'} .= 'print "\n";' . "\n" if ($string =~ /\n$/);
}
#####################################
sub EatParam {
	my ($self, $in) = @_;
	my $tokens = $tokenizers{$in};
	my $line = $self->Line;
	my $state = '';
	my $text = '';
	my @tokens;
	for (;;) {
		my $ch;
		if ($self->{'source'} =~ s/^(.)//s) {
			$ch = $1;
		} else {
			$self->Die("Could not close tag $in, probably unbalanced quotes");
		}
		if ($ch eq "\0") {
			unless ($self->{'source'} =~ s/^(.*?)\0//) {
				$self->Die("Unclosed null encpasulation. Check your macro");
			}
			$text .= $1;
			next;
		}
		if ($ch eq "'" && $state ne '"') {
			$text .= "\\'";
			$state = ($state eq "'" ? '' : "'");
			next;
		}
		if ($ch eq '"' && $state ne "'") {
			$text .= "\\\"";
			$state = ($state eq '"' ? '' : '"'); #'"
			next;
		}
		if ($ch eq "\\") {
			$self->{'source'} =~ s/^(.)//s;
			$ch = $1;
			$text .= "\\$ch";
			next;
		}
		if ($ch eq '>' && !$state) {
			$text =~ s/\s+$//;
			return $text unless $tokens;	
			return [] unless @tokens;
			my $pre = shift @tokens;
			$self->Die("Illegal prefix $pre") if $pre;
			push(@tokens, $text);
			return \@tokens;
		}
		if ($ch eq '.' && !$state && $tokens) {
			push(@tokens, $text);
			$text = '';
			next;
		}
		if ($ch eq "<") {
			unless ($self->{'source'} =~ s/^$open//) {
				$text .= "<";
				$text .= $self->FindRight if $in eq 'EM';
				next;
			}
			$self->{'source'} =~ s/(\[.+?\]\.)?(\w+)//;
			my $engine = $1;
			my $tag = uc($2);
			$engine =~ s/^\[(.*)\]\./$1/;
			$engine= $self->Clause($engine,$tag) if($engine=~ /\<$open/);
			my $code;
			if ($in ne 'EM') {
				$code = $self->WantPrinter($tag, $in, $line);
			}
			my $sub = $self->EatParam($in eq 'EM' ? 'EM' : $tag);
			if ($in ne 'EM') {
				$text .= '" . (' . &$code($self, $engine, $sub) . ') . "';
			}
		} else {
			$text .= quotemeta($ch);
		}
	}
}
#####################################
sub FindRight {
	my $self = shift;
	my $count = 1;
	my $text;
	while ($self->{'source'} =~ s/^(.*?)([\<\>])//) {
		$text .= "$1$2";
		$count += $2 eq '<' ? 1 : -1;
		return $text unless $count;
	}
	return $text;
}
#####################################
sub Expect {
	my ($self, $engine, @options) = @_;
	my $current = pop @{$self->{'scopes'}};
	my @topt = @options;
	my $last = pop @topt;
	my $expect = join(", ", @topt) . (@topt ? ' or ' : '') . $last;
	$self->Die("Stack underflow - a closing tag without a preceding tag, expecting: $expect. Perhaps you forgot $open in the opening tag?") unless ($current);
	my ($scope, $teng) = @$current;
	$self->Die("Expected engine '$engine', got '$teng'") unless ($teng eq $engine);
	foreach (@options) {
		return if ($_ eq $scope);
	}
	$self->Die("Unexpected scope $scope, expecting: $expect. Perhaps you forgot $open in the opening tag?");
}
#####################################
sub Push {
	my ($self, $scope, $engine) = @_;
	push(@{$self->{'scopes'}}, [$scope, $engine]);
}
#####################################
sub DoLOOP {
	my ($self, $engine, $param) = @_;
	my $limit = undef;
	if ($param =~ s/^\\\.LIMIT\\=((?:\\['"])?)(.+)\1$//s) { #'
		$limit = $2;
	}
	$self->Syntax if $param;
	my $text;
	unless ($limit) {
		$text = <<EOM;
local (\$_);
for (;;) {
	\$_++;
EOM
	} else {
		$text = <<EOM;
HTML::Merge::Engine::Force("$limit", 'iu');
foreach (1 .. "$limit") {
EOM
	}
	$text .= <<EOM;
	last unless (\$engines{"$engine"}->HasQuery);
	last unless (\$engines{"$engine"}->Fetch(1, \$_));
	local (\$_);
EOM
	$self->Push('loop', $engine);
	$text;
}
#####################################

*DoEPEAT = \&DoITERATION;
*DoUnEPEAT = \&DoUnITERATION;

#####################################
sub DoITERATION {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.LIMIT\\=((?:\\['"])?)(.+)\1$/s) { #'
		$self->Syntax;
	}
	my $limit = $2;
	$self->Push('iteration', $engine);
<<EOM;
HTML::Merge::Engine::Force("$limit", 'ui');
foreach (1 .. "$limit") {
EOM
}
#####################################
sub DoUnITERATION {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'iteration');
	"}\n";
}
#####################################
sub DoBREAK {
	my ($self, $engine, $param) = @_;
	$self->Syntax if ($param);
	"last;";
}

#####################################
sub DoCONT {
	my ($self, $engine, $param) = @_;
	$self->Syntax if ($param);
	"next;";
}
#####################################
sub DoUnLOOP {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'loop');
	"}\n";
}
#####################################
sub DoFETCH {
	my ($self, $engine, $param) = @_;
	$self->Syntax if ($param);
	"\$engines{\"$engine\"}->Fetch(1, 2);";
}
#####################################

*DoENVGET = \&DoENV;

#####################################
sub DoENV {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+)$//s) {
		$self->Syntax;
	}
	return "\$ENV{\"$1\"}";
}
#####################################
sub DoENVSET {
        my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+?)\\=\\(['"])(.*?)\\\2$//s) {
		$self->Syntax;
	}
	"\$ENV{\"$1\"} = eval(\"$3\");\n";
}
#####################################
sub DoCFG {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+)$//s) {
		$self->Syntax;
	}
	"\${\"HTML::Merge::Ini::\"  . \"$1\"}";
}
#####################################

*DoINIGET = *DoINI = *DoCFGGET = \&DoCFG;
*DoINISET = \&DoCFGSET;

#####################################
sub DoCFGSET {
        my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+?)\\=\\(['"])(.*)\\\2$//s) {
		$self->Syntax;
	}
	"\${\"HTML::Merge::Ini::\"  . \"$1\"} = eval(\"$3\");\n";
}
#####################################

*DoVAL = \&DoVAR;

#####################################
sub DoVAR 
{
	my ($self, $engine, $param) = @_;

	unless ($param =~ s/^\\\.(.+)$//s) 
	{
		$self->Syntax;
	}

	return "\$vars{\"$1\"}";
}
#####################################
sub DoVERSION 
{
	my ($self, $engine, $param) = @_;

	return $VERSION;
}
#####################################
sub DoSQL 
{
	my ($self, $engine, $param) = @_;

	unless ($param =~ s/^\\\.(.+)$//s) 
	{
		$self->Syntax;
	}

	return "\$engines{\"$engine\"}->Var(\"$1\")";
}
#####################################
sub DoIF 
{
	my ($self, $engine, $param) = @_;

	unless ($param =~ s/^\\[\.=]\\(['"])(.*)\\\1$//s) 
	{
		$self->Syntax;
	}

	my $text = <<EOM;
HTML::Merge::Error::HandleError('INFO', "$2", 'IF');
my \$__test = eval("$2");
HTML::Merge::Error::HandleError('ERROR', \$@) if (\$@);
if (\$__test) {
EOM
	$self->Push('if', $engine);
	$text;
}
#####################################
sub DoTIF 
{
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\[\.=]\\(['"])(.*)\\\1$//s) 
	{
		$self->Syntax;
	}

	my $text = <<EOM;
HTML::Merge::Error::HandleError('INFO', "$2", 'IF');
my \$__test = "$2";
HTML::Merge::Error::HandleError('ERROR', \$@) if (\$@);
if ("$2") {
EOM
	$self->Push('if', $engine);
	$text;
}
#####################################
sub DoUnTIF {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'if', 'else');
	"}\n";
}
#####################################
sub DoELSIF {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\[\.=]\\(['"])(.*)\\\1$//s) {
		$self->Syntax;
	}
	$self->Expect($engine, 'if');
	$self->Push('if', $engine);
	my $text = <<EOM;
	\$__exit = 0;
} elsif (((HTML::Merge::Error::HandleError('INFO', "$2", 'IF'),
	\$__exit = eval("$2"),
	\$@ && HTML::Merge::Error::HandleError('ERROR', \$@),
	\$__exit))[-1]) {
EOM
	$text;
}


sub DoUnIF {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'if', 'else');
	"}\n";
}

sub DoELSE {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	$self->Expect($engine, 'if');
	$self->Push('else', $engine);
	"} else {\n";
}

sub DoWHILE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\[\.=]\\(['"])(.*)\\\1$//s) {
		$self->Syntax;
	}
	my $cond = quotemeta($2);
	my $text = <<EOM;
HTML::Merge::Error::HandleError('INFO', "while $2", 'WHILE');
for (;;) {
	my \$__test = eval("$2");
	HTML::Merge::Error::HandleError('ERROR', \$@) if (\$@);
	last unless \$__test;
EOM
	$self->Push('while', $engine);
	$text;
}

sub DoUnWHILE {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'while');
	"}\n";
}

sub DoQ {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\[=\.]\\(['"])(.*)\\\1$//s) {
		$self->Syntax;
	}
	"\$engines{\"$engine\"}->Query(\"$2\");\n";
}

sub DoS {
        my ($self, $engine, $param) = @_;
        unless ($param =~ s/^\\[\.=]\\(['"])(.*)\\\1$//s) {
                $self->Syntax;
        }
        "\$engines{\"$engine\"}->Statement(\"$2\");\n";
}

sub DoEVAL {
        my ($self, $engine, $param) = @_;
        unless ($param =~ s/^\\[\.=]\\(['"])(.*)\\\1$//s) {
                $self->Syntax;
        }
        "eval(\"$2\")";
}
#####################################
sub DoPERL {
        my ($self, $engine, $param) = @_;
	my $type;
	if ($param =~ s/^\\\.([ABC])$//i) {
		$type = uc($1);
	}
	$self->Syntax if $param;
	my $code = "";
	my $line = $self->Line;
	if ($type eq 'B' || $type eq 'C') {
		my $flag;
		while ($self->{'source'} =~ s/^(.*?)\<($open(?:\[.+?\]\.)?\w+|\/${open}PERL\>)//is) {
			my $let = quotemeta($1);
			$code .= qq!"$let" . !;
			my $tag = $2;
			if ($tag =~ m|^/${open}PERL>$|) {
				$flag = 1;
				last;
			}
			$tag =~ s/^$open//;
			my $engine = '';
			if ($tag =~ s/^\[(.+?)\]\.//) {
				$engine = $1;
				$engine= $self->Clause($engine,$tag) if($engine=~ /\<$open/);
			}
			my $coder = $self->WantPrinter($tag, "PERL", $line);
			my $param = $self->EatParam($tag);
			my $codet = &$coder($self, $engine, $param);
			$code .= "$codet . ";
		}
		$self->Die("End of PERL not found") unless $flag;
		$code .= q!""!;
	} else {
		unless ($self->{'source'} =~ s/^(.*?)\<\/${open}PERL\>//is) {
			$self->Die("End of PERL not found");
		}
		$code = '"' . quotemeta($1) . '"';
	}
	my $name = $self->{'name'};
	my $text = <<EOM;
\$__result = $code;
HTML::Merge::Error::HandleError('INFO', \$__result, 'PERL');
\$__result = eval("\$__result; undef;");
HTML::Merge::Error::HandleError('ERROR', \$@) if \$@;
EOM
	if ($type eq 'A' || $type eq 'C') {
		$line = $self->Line;
		$text .= <<EOM;
if (\$__result) {
	use HTML::Merge::Compile;
	eval { \$__result = &HTML::Merge::Compile::Compile(\$__result, "$name", $line); };
	HTML::Merge::Error::HandleError('ERROR', \$@) if \$@;
	\$__result = eval(\$__result);
	HTML::Merge::Error::HandleError('ERROR', \$@) if \$@;
}
EOM
	}
	$text;
}
###############################################################
sub DoSET 
{
        my ($self, $engine, $param) = @_;

	unless ($param =~ s/^\\\.(.+?)\\=\\(['"])(.*?)\\\2$//s) 
	{
		$self->Syntax;
	}

	return "\$vars{\"$1\"} = eval(\"$3\");\n";
}
###############################################################
sub DoASSIGN 
{
        my ($self, $engine, $param) = @_;

	unless ($param =~ s/^\\\.(.+?)\\=\\(['"])(.*?)\\\2$//s) 
	{
		$self->Syntax;
	}

	return "\$vars{\"$1\"} = \"$3\";\n";
}
###############################################################

sub DoPCLEAR {
        my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	"\$engines{\"$engine\"}->ErasePersistent;\n";
}

sub DoPSET {
        my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+?)\\=\\(['"])(.*?)\\\2$//s) {
		$self->Syntax;
	}
	"\$engines{\"$engine\"}->SetPersistent(\"$1\", eval(\"$3\"));\n";
}

sub DoPGET {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+)$//s) {
		$self->Syntax;
	}
	return "\$engines{\"$engine\"}->GetPersistent(\"$1\")";
}

*DoPVAR = \&DoPGET;
*DoGET = \&DoVAR;

sub DoPIMPORT {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+)$//s) {
		$self->Syntax;
	}
	return "\$hash{\"$1\"} = \$engines{\"$engine\"}->GetPersistent(\"$1\");";
}

sub DoPEXPORT {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.+)$//s) {
		$self->Syntax;
	}
	return "\$engines{\"$engine\"}->SetPersistent(\"$1\", \$hash{\"$1\"});";
}


*DoREM = \&DoEM;
sub DoEM {}

sub DoTRACE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.\\(['"])(.*)\\\1$//s) {
		$self->Syntax;
	}
	my $line = $2;
	<<EOM;
HTML::Merge::Error::HandleError('INFO', "$line", 'TRACE');
EOM
}
sub DoDIE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.\\(['"])(.*)\\\1$//s) {
		$self->Syntax;
	}
	my $line = $2;
	<<EOM;
HTML::Merge::Error::HandleError('ERROR', "$line");
EOM
}
#################################################
sub DoINCLUDE 
{
	my ($self, $engine, $param) = @_;
	my $inc;
	my $name = $self->{'name'};
	my $text;

	unless ($param =~ s/^\\\.\\(['"])(.*)\\\1$//s) 
	{
		$self->Syntax;
	}
	$inc = $2;
	$inc =~ s/\\(.)/$1/g;

##################################################################
#	require Cwd;
#	my $curr = &Cwd::cwd;
#	my @tokens = split(/\//, $self->{'name'});
#	pop @tokens;
#	my $dir = join("/", @tokens);
#	chdir $dir if $dir;
#	open(I, $inc) || $self->Die("Can't open $inc at $dir");
#	my $text = join("", <I>);
#	close(I);
#	chdir $curr;
#	$self->{'source'} = $text . $self->{'source'};
##################################################################

	$text = <<EOM;
	my \$__input = HTML::Merge::Compile::GetTemplateFromPath("$inc");
	my \$__script = "\$HTML::Merge::Ini::CACHE_PATH/$inc.pli";
	my \$__candidate = "\$HTML::Merge::Ini::PRECOMPILED_PATH/$inc.pli";

	unless (-e \$__candidate) 
	{
		#HTML::Merge::Error::DoWarn('NO_TEMPLATE','$inc') unless -e \$__input;
		HTML::Merge::Error::HandleError('ERROR',
			"No template '$inc' found") unless -e \$__input;

		my \$__source = (stat(\$__input))[9];
		my \$__output = (stat(\$__script))[9];
		if (\$__source > \$__output) {
			require HTML::Merge::Compile;
			HTML::Merge::Compile::safecreate(\$__script)
				unless -e \$__script;
			eval '	HTML::Merge::Compile::CompileFile(\$__input, \$__script, 1); ';

			if(\$@)
			{
				# erase the pli file
				unlink(\$__script);
				HTML::Merge::Error::HandleError('ERROR', \$@);
			}
		}
	} else {
		\$__script = \$__candidate;
	}
	HTML::Merge::Error::HandleError('INFO',"$inc",'INCLUDE');
	do \$__script;
	HTML::Merge::Error::HandleError('ERROR', \$@) if \$@;
EOM
	$text;
}
#################################################
sub DoWEBINCLUDE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.\\(['"])(.*)\\\1$//s) {
		$self->Syntax;
	}
	my $url = $2;
<<EOM;
if (\$HTML::Merge::Ini::WEB) {
	require LWP;
	require HTTP::Request::Common;
	import HTTP::Request::Common;

	my \$__url = "$url";
	\$__url = "http://\$ENV{'SERVER_NAME'}:\$ENV{'SERVER_PORT'}\$__url"
		unless (\$__url =~ m|://|);
	my \$__ua = new LWP::UserAgent;
	my \$__req = GET("$url");
	my \$__resp = \$__ua->request(\$__req);
	if (\$__resp->is_success) {
		print \$__resp->content;
	} else {
		HTML::Merge::Error::HandleError('ERROR', "Web GET to URL $url returned code " . \$__resp->code);
	}
}
EOM
}

sub DoINDEX {
        my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	"\$engines{\"$engine\"}->Index";
}

*DoRERUN = \&DoERUN;

sub DoERUN {
        my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	"\$engines{\"$engine\"}->ReRun;";
}

*EQUEST = \&ENUMREQ;

sub DoENUMREQ {
	my ($self, $engine, $param) = @_;
	$self->Syntax unless ($param =~ /^\\\.(.+?)\\\=(.+)$/s);
	my ($iterator, $getter) = ($1, $2);
	$self->Push('enumreq', $engine);
	qq!foreach (param()) {
				next if (\$_ eq "template");
				\$vars{"$iterator"} = \$_;
				\$vars{"$getter"} = \$vars{\$_};\n!;
}

sub DoUnENUMREQ {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'enumreq');
	"}\n";
}

sub DoENUMQUERY {
	my ($self, $engine, $param) = @_;
	$self->Syntax unless ($param =~ /^\\\.(.+?)\\\=(.+)$/s);
	my ($iterator, $getter) = ($1, $2);
	$self->Push('enumquery', $engine);
	qq!foreach (\$engines{"$engine"}->Columns) {
				\$vars{"$iterator"} = \$_;
				\$vars{"$getter"} = \$engines{"$engine"}->Var(\$_);\n!;
}

sub DoUnENUMQUERY {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'enumquery');
	"}\n";
}

sub DoMULTI {
	my ($self, $engine, $param) = @_;
	$self->Syntax unless ($param =~ /^\\\.(.+?)\\\=(.+)$/s);
	my ($iterator, $getter) = ($1, $2);
	$self->Push('multi', $engine);
	qq!foreach (param("$getter")) {
				\$vars{"$iterator"} = \$_;!;
}

sub DoUnMULTI {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'multi');
	"}\n";
}

sub DoGLOB {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.([DF])\\\.(.+?)\\=\\(['"])(.*)\\\3$/is) {
		$self->Syntax;
	}
	my ($how, $iterator, $mask) = (uc($1), $2, $4);
	$self->Push('glob', $engine);
	my $cond = $how eq 'D' ? 'unless' : 'if';
	qq!\$__x = "$mask";
		\$__x .= "/*" if (-d \$__x);
		foreach (glob(\$__x)) {
				next $cond -d \$_;
				s|^.*/||;
				\$vars{"$iterator"} = \$_;\n!
}

sub DoUnGLOB {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'glob');
	"}\n";
}

sub DoFTS {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.(.+?)\\=\\(['"])(.*)\\\2$/is) {
		$self->Syntax;
	}
	my ($iterator, $base) = ($1, $3);
	$self->Push('fts', $engine);
	<<EOM;
	use File::Find;
	\@__files = ();
	find(sub {push(\@__files, \$File::Find::name)}, "$base");
	foreach (\@__files) {
		\$vars{"$iterator"} = \$_;
EOM
}

sub DoUnFTS {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'fts');
	"}\n";
}

sub DoCOUNT {
	my ($self, $engine, $param) = @_;
	$self->Syntax unless ($param =~ /^\\\.(.+?)\\\=(.*?)\\\:(.*?)(\\,.*)?$/s);
	my ($var, $from, $to, $step) = ($1, $2, $3, $4);
	$step ||= "\\,1";
	$step =~ s/^\\,//;

	my $i = "\$vars{\"$var\"}";
	$self->Push('count', $engine);
	<<EOM;
	HTML::Merge::Engine::Force("$from", "n");
	HTML::Merge::Engine::Force("$to", "n");
	HTML::Merge::Engine::Force("$step", "n");
	for ($i = "$from"; $i <= "$to"; $i += "$step") {
EOM
}

sub DoUnCOUNT {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'count');
	"}\n";
}

sub DoPIC {
	my ($self, $engine, $param) = @_;
	my $type;
	unless ($param =~ s/^\\\.([CFRNADX])(.*)$//is) {
		$self->Syntax;
	}
	($type, $param) = (uc($1), $2);
	my $code = &UNIVERSAL::can($self, "Picture$type");
	&$code($self, $param);
}

sub PictureF {
	my ($self, $param) = @_;
	$param =~ s/^\\\((\\?.)\\\)\\\.\\(['"])(.*?)\\\2$/$1\\$2$3\\$2/s;
	unless ($param =~ /^(\\?.)\\(['"])(.*?)\\\2$/s) {
		$self->Syntax;
	}
	my ($ch, $text) = ($1, $3);
	<<EOM;
"" . (\$__s = "$text", \$__s =~ s/\\s/$ch/g, \$__s)[-1]
EOM
}

sub PictureC {
	my ($self, $param) = @_;
	my @ary;
	my $flag;
	$param =~ s/^\\\((.*)\\\)\\\.\\(['"])(.*?)\\\2$/$1\\.\\$2$3\\$2/s;
	while ($param =~ 
			s/^\s*\\(['"])(.*?)\\\1\s*\\=\s*\\(['"])(.*?)\\\3\s*//s) {
		push(@ary, [$2, $4]);
		if ($param =~ s/^\\\.//) {
			$flag = 1;
			last;
		}
		unless ($param =~ s/^\\,//) {
			$self->Syntax;
		}
	}
	$self->Die("Syntax error in PIC.C") unless ($flag);
	unless ($param =~ s/^\\(["'])(.*?)\\\1$//s) {
		$self->Syntax;
	}
	my $text = $2;
	my $code = <<EOM;
"" . (\$__s = "$text",
EOM
	foreach (@ary) {
		my ($from, $to) = @$_;
		$code .= <<EOM;
\$__s =~ s/^$from\$/$to/g,
EOM
	}
	$code . ", \$__s)[-1]";
}

sub PictureR {
	my ($self, $param) = @_;
	my @ary;
	my $flag;
	$param =~ s/^\\\((.*)\\\)\\\.\\(['"])(.*?)\\\2$/$1\\.\\$2$3\\$2/s;
	while ($param =~ 
			s/^\s*\\(['"])(.*?)\\\1\s*\\=\s*\\(['"])(.*?)\\\3\s*//s) {
		push(@ary, [$2, $4]);
		if ($param =~ s/^\\\.//) {
			$flag = 1;
			last;
		}
		unless ($param =~ s/^\\,//) {
			$self->Syntax;
		}
	}
	$self->Die("Syntax error in PIC.R") unless ($flag);
	unless ($param =~ s/^\\(["'])(.*?)\\\1$//s) {
		$self->Syntax;
	}
	my $text = $2;
	my $code = <<EOM;
"" . (\$__s = "$text",
EOM
	foreach (@ary) {
		my ($from, $to) = @$_;
		$code .= <<EOM;
\$__s =~ s/$from/$to/g,
EOM
	}
	$code . ", \$__s)[-1]";
}

sub PictureN {
	my ($self, $param) = @_;
	my %opts;
	while ($param =~ s/^([ZF])//) {
		$opts{$1}++;
	}
	unless ($param =~ s/^\\\((.*?)\\\)//s) {
		$self->Syntax;
	}
	my $format = $1;
	unless ($param =~ s/^\\\.\\(["'])(.*?)\\\1$//s) {
		$self->Syntax;
	}
	my $text = $2;
	<<EOM;
"" . (\$__s = "$text" || !"$opts{'Z'}" ? sprintf("%${format}f", "$text") : "&nbsp;",
	"$opts{'F'}" ? (\$__s =~ 
	s!(\\d+)!scalar(reverse join(\$HTML::Merge::Ini::THOUSAND_SEPARATOR || ",", (reverse \$1) =~ /(\\d{1,3})/g))!e) : undef, 
	\$__s =~ s/\\./\$HTML::Merge::Ini::DECIMAL_SEPARATOR || '.'/e,
	\$__s)[-1]
EOM
}

sub PictureA {
	my ($self, $param) = @_;
	my %opts;
	while ($param =~ s/^([LRCSPWDE])//) {
		$opts{$1}++;
	}
	foreach (qw(SCP DE)) {
		my $count;
		foreach (split(//)) {
			$self->Die("Illegal flag combinations") 
				if ($opts{$_} && $count++);
		}
	}
	unless ($param =~ s/^\\\((.*?)\\\)//s) {
		$self->Syntax;
	}
	my $format = $1;
	unless ($param =~ s/^\\\.\\(["'])(.*?)\\\1$//s) {
		$self->Syntax;
	}
	my $text = $2;
	<<EOM;
"" . (\$__s = "$text",
	"$opts{'C'}" && \$__s =~ tr/a-z/A-Z/,
	"$opts{'S'}" && \$__s =~ tr/A-Z/a-z/,
	"$opts{'P'}" && \$__s =~ s/\\b([a-z]\\S+)/ucfirst(lc(\$1))/egi,
	"$opts{'L'}" && \$__s =~ s/^\\s+//,
	"$opts{'R'}" && \$__s =~ s/\\s+\$//,
	"$opts{'W'}" && \$__s =~ s/\\s{2,}/ /g,
	"$opts{'E'}" && (\$__s =~ s/([^ _A-Za-z0-9-\\/])/sprintf("%%%02X", ord(\$1))/ge, \$__s =~ s/ /+/g),
	"$opts{'D'}" && (\$__s =~ s/\\+/ /g, \$__s =~ s/%(..)/chr(hex(\$1))/ge),
	sprintf("%${format}s", \$__s))[-1]
EOM
}

sub PictureD {
	my ($self, $param) = @_;
	unless ($param =~ s/^\\\((.*?)\\\)//s) {
		$self->Syntax;
	}
	my $format = $1;
	unless ($param =~ s/^\\\.\\(["'])(.*?)\\\1$//s) {
		$self->Syntax;
	}
	my $date = $2;

	<<EOM;
(require Time::Local, 
("$date") =~ /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})\$/,
	\$__t = Time::Local::timelocal(\$6, \$5, \$4, \$3, \$2 - 1, \$1 - 1900),
	HTML::Merge::Engine::time2str("$format", \$__t))[-1]
	
EOM
}

sub PictureX {
	my ($self, $param) = @_;
	unless ($param =~ s/^\\\((.*?)\\\)//s) {
		$self->Syntax;
	}
	my $times = $1;
	unless ($param =~ s/^\\\.\\(["'])(.*?)\\\1$//s) {
		$self->Syntax;
	}
	my $text = $2;
	<<EOM;
(HTML::Merge::Engine::Force("$times", 'ui'),
	"$text" x "$times")[-1]
EOM
}

sub DoINC {
        my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.(.*?)(\\[+-]\d+)?$/s) {
		$self->Syntax;
	}
	my ($var, $step) = ($1, defined($2) ? $2 : 1);
	<<EOM;
HTML::Merge::Engine::Force("$step", "n");
HTML::Merge::Engine::Force(\$vars{"$var"}, "n");
\$vars{"$var"} += "$step";
EOM
}

sub DoSTATE {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	"\$engines{\"$engine\"}->State";
}

sub DoEMPTY {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	"\$engines{\"$engine\"}->Empty";
}

sub DoMAIL {
	my ($self, $engine, $param) = @_;
        unless ($param =~ /^\\\.\\(['"])(.*?)\\\1\\([\.,])\\(['"])(.*?)\\\4(.*)$/s) {
		$self->Syntax;
	}
	my $del = quotemeta($3);
	my ($from, $to, $rem, $subject) = ($2, $5, $6);
	if ($rem) {
		unless ($rem =~ /^\\$del\\(['"])(.*?)\\\1$/s) {
			$self->Syntax;
		}
		$subject = $2;
	}
	$self->Push('mail', $engine);
<<EOM;
	\$__from = "$from";
	\$__from =~ s/^.*\<(.*)\>\$/\$1/;
	\$__from =~ s/^(.*?)\\s+\(\".*\"\)\$/\$1/;
	\$__to = "$to";
	\$__to =~ s/^.*\<(.*)\>\$/\$1/;
	\$__to =~ s/^(.*?)\\s+\(\".*\"\)\$/\$1/;
	use HTML::Merge::Mail;
	eval '\$__mail = OpenMail(\$__from, \$__to, \$HTML::Merge::Ini::SMTP_SERVER);';

	HTML::Merge::Error::HandleError('WARN', 'Mail failed: \$\@') if \$\@;
	\$__prev = select \$__mail;

	print "From: $from\\r\\n";
	print "To: $to\\r\\n";
	print "Subject: $subject\\r\\n";
	print "X-Mailer: Merge v. $VERSION (c) http://www.raz.co.il\\r\\n";
	print "\\r\\n";
EOM
}
sub DoUnMAIL {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'mail');
	<<EOM;
	eval ' CloseMail(\$__mail); ';
        HTML::Merge::Error::HandleError('WARN', 'Mail failed: \$\@') if \$\@;
	select \$__prev;
EOM
}
#####################################
sub DoDB 
{
	my ($self, $engine, $param) = @_;
	
	my ($type, $db, $host);
	my ($dsn,$dsn1, $user, $pass);

	$INTERNAL_DB="dbname=$HTML::Merge::Ini::MERGE_ABSOLUTE_PATH/merge.db";

	unless ($param =~ /^\\[\.=]\\(['"])(.*?)\\\1$/s) 
	{
		$self->Syntax;
	}

	$dsn = $2;
	($dsn1, $user, $pass) = split(/\s*\\,\s*/, $dsn);

	unless ($dsn1) 
	{
		$self->Die("DSN not specified");
	}

	for($dsn)
	{
		if(/^SYSTEM$/)
		{
			if($HTML::Merge::Ini::SESSION_DB)
			{
				$type = $HTML::Merge::Ini::DB_TYPE;
				$db = $HTML::Merge::Ini::SESSION_DB;
				$host = $HTML::Merge::Ini::DB_HOST;
				$user = $HTML::Merge::Ini::DB_USER;
				$pass = $HTML::Merge::Ini::DB_PASSWORD;
			}
			else
			{			
				$type=$INTERNAL_DB_TYPE;
				$db="$INTERNAL_DB";
			}
			last;
		}
		if(/^DEFAULT$/)
		{
			$type = $HTML::Merge::Ini::DB_TYPE;
			$db = $HTML::Merge::Ini::DB_DATABASE;
			$host = $HTML::Merge::Ini::DB_HOST;
			$user = $HTML::Merge::Ini::DB_USER;
			$pass = $HTML::Merge::Ini::DB_PASSWORD;
			last;
		}
		else		
		{
			$dsn1 =~ s/^dbi\\://;
			($type, $db, $host) = split(/\\:/, $dsn1);
			($type, $db) = (undef, $type) unless ($db);
			last;
		}
	}

	<<EOM;
\$engines{"$engine"}->Preconnect("$type", "$db", "$host", "$user", "$pass");
EOM
}
#####################################
sub DoDISCONNECT {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	qq!delete \$engines{"$engine"};!;
}

sub DoEXIT {
	my ($self, $engine, $param) = @_;
	$self->Die if $param;
	"die 'STOP_ON_ERROR';\n";
}

sub DoLOGIN {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[\.=]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($user, $pass) = ($2, $4);
	qq!\$engines{"$engine"}->Login("$user", "$pass")!;
}

sub DoCHPASS {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[\.=]\\(['"])(.*?)\\\1$/s) {
		$self->Syntax;
	}
	qq!\$engines{"$engine"}->ChangePassword("$2");!;
}

sub DoAUTH {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*?)\\\1$/s) {
		$self->Syntax;
	}
	qq!\$engines{"$engine"}->HasKey("$2")!;
}

sub DoADDUSER {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[\.=]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($user, $pass) = ($2, $4);
	qq!\$engines{"$engine"}->AddUser("$user", "$pass");!;
}

sub DoDELUSER {
        my ($self, $engine, $param) = @_;
        unless ($param =~ /^\\[=\.]\\(['"])(.*?)\\\1$/s) {
                $self->Syntax;
        }
        my ($user) = ($2);
	qq!\$engines{"$engine"}->DelUser("$user");!;
}

sub DoJOIN {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($user, $group) = ($2, $4);
	qq!\$engines{"$engine"}->JoinGroup("$user", "$group");!;
}

sub DoPART {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($user, $group) = ($2, $4);
	qq!\$engines{"$engine"}->PartGroup("$user", "$group");!;
}

sub DoGRANT {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]([UG])\\\.\\(['"])(.*?)\\\2\\\,\\(['"])(.*?)\\\4$/si) {
		$self->Syntax;
	}
	my ($how, $who, $realm) = (uc($1), $3, $5);
	if ($how eq 'U') {
		return qq!\$engines{"$engine"}->GrantUser("$who", "$realm");!;
	}
	if ($how eq 'G') {
		return qq!\$engines{"$engine"}->GrantGroup("$who", "$realm");!;
	}
}

*DoREVOKE = \&DoEVOKE;

sub DoEVOKE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]([UG])\\\.\\(['"])(.*?)\\\2\\\,\\(['"])(.*?)\\\4$/si) {
		$self->Syntax;
	}
	my ($how, $who, $realm) = (uc($1), $3, $5);
	if ($how eq 'U') {
		return qq!\$engines{"$engine"}->RevokeUser("$who", "$realm");!;
	}
	if ($how eq 'G') {
		return qq!\$engines{"$engine"}->RevokeGroup("$who", "$realm");!;
	}
}

sub DoATTACH {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($template, $subsite) = ($2, $4);
	qq!\$engines{"$engine"}->Attach("$template", "$subsite");!;
}

sub DoDETACH {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($template, $subsite) = ($2, $4);
	qq!\$engines{"$engine"}->Detach("$template", "$subsite");!;
}


*DoREQUIRE = \&DoEQUIRE;

sub DoEQUIRE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\[=\.]\\(['"])(.*?)\\\1\\\,\\(['"])(.*?)\\\3$/s) {
		$self->Syntax;
	}
	my ($template, $realms) = ($2, $4);
	qq!\$engines{"$engine"}->Require("$template", "$realms");!;
}

sub DoUSER {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	qq!\$engines{"$engine"}->GetUser!;
}

sub DoNAME {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	qq!scalar(\$engines{"$engine"}->GetUserName)!;
}

sub DoTAG {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	qq!(\$engines{"$engine"}->GetUserName)[1]!;
}

sub DoMERGE {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	'"$HTML::Merge::Ini::MERGE_PATH/$HTML::Merge::Ini::MERGE_SCRIPT"';
}

sub DoTEMPLATE {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	qq!\$HTML::Merge::template!;
}

sub DoTRANSFER {
	my ($self, $engine, $param) = @_;
	my $validate;
	unless ($param =~ s/^\\\.(.+)$//s) {
		$self->Syntax;
	}
	qq!qq/<INPUT NAME="$1" TYPE=HIDDEN VALUE="\$vars{"$1"}">/!;
}

sub DoSUBMIT {
	my ($self, $engine, $param) = @_;
	my $validate;
	if ($param =~ s/^\\\.\\(["'])(.*)\\\1$//s) {
		$validate = " onSubmit=\"$2\"";
	}
	$self->Syntax if $param;
	$self->Push('submit', $engine);
	<<EOM;
print qq!<FORM ACTION="\$HTML::Merge::Ini::MERGE_PATH/\$HTML::Merge::Ini::MERGE_SCRIPT" METHOD=POST NAME="autoform"$validate>
<INPUT NAME="template" TYPE=HIDDEN VALUE="\$HTML::Merge::template">!;
EOM
}

sub DoUnSUBMIT {
	my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'submit');
	qq!print "</FORM>\\n";!;
}

sub DoDECIDE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*?)\\\1\\\?\\(['"])(.*?)\\\3\\\:\\(['"])(.*?)\\\5$/s) {
		$self->Syntax;
	}
	my ($decision, $true, $false) = ($2, $4, $6);
	<<EOM;
	(
		(eval("$decision") ? "$true" : "$false"),
		\$@ && HTML::Merge::Error::HandleError('ERROR', \$@)
	)[0]
EOM
}

sub DoDATE {
	my ($self, $engine, $param) = @_;
	my $delta = 0;
	if ($param =~ s/^\\[,\.]((?:\\-)?\d+)$//s) {
		$delta = $1;
	}
	$self->Syntax if $param;
	<<EOM;
(HTML::Merge::Engine::Force("$delta", 'i'),
	\@__t = localtime(time + "$delta" * 3600 * 24), 
	sprintf("%04d" . ("%02d" x 5), \$__t[5] + 1900, \$__t[4] + 1,
		\@__t[reverse (0 .. 3)]))[-1]
EOM
}

sub DoDAY {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	qq{substr("$2", 6, 2) * 1};
}

sub DoMONTH {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	qq{substr("$2", 4, 2) * 1};
}

sub DoYEAR {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	qq{substr("$2", 0, 4)};
}

sub DoMINUTE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	qq{substr("$2", 10, 2) * 1};
}

sub DoHOUR {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	qq{substr("$2", 8, 2) * 1};
}


sub DoSECOND {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	qq{substr("$2", 12, 2) * 1};
}

sub DoDATEDIFF {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.([HSMD])\\\.(\\['"])?(.*)\2\\,(\\['"])?(.*)\4$/s) {
		$self->Syntax;
	}
	my ($how, $before, $now) = ($1, $3, $5);
	my %hash = qw(S 1 M 60 H 3600 D 86400);
	my $div = $hash{$how} || 1;
	<<EOM;
(require Time::Local, 
\$__conv = sub { (shift() =~ /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})/); 
	Time::Local::timelocal(\$6, \$5, \$4, \$3, \$2 - 1, \$1 - 1900); },
int((&\$__conv("$now") - &\$__conv("$before")) / $div))[-1]
EOM
}

sub DoDATE2UTC {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	<<EOM;
(require Time::Local, 
("$2") =~ /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})\$/,
	Time::Local::timelocal(\$6, \$5, \$4, \$3, \$2 - 1, \$1 - 1900))[-1]
EOM
}

sub DoUTC2DATE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/) {
		$self->Syntax if $param;
	}
	<<EOM;
(HTML::Merge::Engine::Force("$2", 'ui'),
	\@__t = localtime("$2"), 
	sprintf("%04d" . ("%02d" x 5), \$__t[5] + 1900, \$__t[4] + 1,
		\@__t[reverse (0 .. 3)]))[-1]
EOM
}

sub DoLASTDAY {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	<<EOM;
((\$__y, \$__m, \$__d) = ("$2" =~ /^(\\d{4})(\\d{2})(\\d{2})/),
\$__base = (qw(31 28 31 30 31 30 31 31 30 31 30 31))[\$__m - 1],
\$__leap = (\$__y % 4) ? 0 
	: ((\$__y % 100) ? 1 
		: ((\$__y % 400) ? 0 : 1)
	),
\$__base + (\$__m == 2 ? \$__leap : 0))[-1]
EOM
}

sub DoADDDATE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1\\\,\\(['"])(.*)\\\3\\,\\(['"])(.*)\\\5\\,\\(['"])(.*)\\\7$/s) {
		$self->Syntax;
	}
	my ($date, $d, $m, $y) = ($2, $4, $6, $8);
	<<EOM;
(require Time::Local,
("$date") =~ /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})/,
\$__t = Time::Local::timelocal(\$6, \$5, \$4, \$3, \$2 - 1, \$1 - 1900)
	+ 3600 * 24 * "$d",
\@__t = localtime(\$__t),
\$__t[4] += "$m", \$__t[5] += "$y", 
\$__t[5] += int(\$__t[4] / 12), \$__t[4] %= 12,
sprintf("%04d" . ("%02d" x 5), \$__t[5] + 1900, \$__t[4] + 1,
                \@__t[reverse (0 .. 3)]))[-1]
EOM
}

sub DoDIVERT {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	my $fn = $2;
	$self->Push('divert', $engine);
	<<EOM;
	push(\@__diverts, select);
	use Symbol;
	\$__sym = gensym;
	open(\$__sym, ">>/tmp/merge-\$\$-$fn.divert") || die \$!;
	select \$__sym;
	push(\@HTML::Merge::cleanups, eval qq!sub { unlink "/tmp/merge-\$\$-$fn.divert" }!);
EOM
	# Value of $fn might contain merge variables, that might change
	# until cleanup time. Therefore compile cleanup function
	# with the filename as part of the source.
}

sub DoUnDIVERT {
	my ($self, $engine, $param) = @_;
	$self->Syntax if $param;
	$self->Expect($engine, 'divert');
	<<EOM;
	\$__sym = select;
	select pop \@__diverts;
	close \$__sym;
EOM
}

sub DoDUMP {
	my ($self, $engine, $param) = @_;
	unless ($param =~ /^\\\.\\(['"])(.*)\\\1$/s) {
		$self->Syntax;
	}
	my $fn = $2;
	<<EOM;
(open(DIVERT_DUMP, "/tmp/merge-\$\$-$fn.divert") || die(\$!), join("", <DIVERT_DUMP>),
	close(DIVERT_DUMP))[1]
EOM
}

*DoCGET = *DoCVAR = \&DoCOOKIE;

sub DoCOOKIE {
	my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.*)$//s) {
		$self->Syntax;
	}
	"\$engines{\"$engine\"}->GetCookie(\"$1\")";
}

*DoCSET = \&DoCOOKIESET;

sub DoCOOKIESET {
        my ($self, $engine, $param) = @_;
	unless ($param =~ s/^\\\.(.*?)\\=\\(['"])(.*?)\\\2((?:\\,.*)?)$//s) {
		$self->Syntax;
	}
	my $expire = substr($4, 2);
	"\$engines{\"$engine\"}->SetCookie(\"$1\", eval(\"$3\"), \"$expire\");";
}

sub DoSOURCE {
        my ($self, $engine, $param) = @_;
	my $file = '$HTML::Merge::template';
	if ($param =~ s/^\\\.\\(['"])(.*)\\\1$//s) {
		$file = $2;
	}
	$self->Syntax if $param;
	$self->Push('source', $engine);
	qq!'<A HREF="' . 
	 	 HTML::Merge::Development::MakeLink('printsource.pl', "template=$file")
				. '" TITLE="view source">'!;
}

sub DoUnSOURCE {
        my ($self, $engine, $param) = @_;
	$self->Expect($engine, 'source');
	qq!"</A>"!;
}

sub safecreate {
        my @tokens = split(/\//, shift);
        pop @tokens;
        my $dir;
        foreach (@tokens) {
                $dir .= "/$_";
                mkdir $dir, 0755;
        }
}
#####################################
sub CompileFile 
{
	my ($file, $out, $sub) = @_;

	my $tmp;
	open(I, $file) || die "Cannot open $file: $!";
	my $text = join("", <I>);
	close(I); 
	
	open(O, ">$out") || die "Can't write $out: $!";
	my $prev = select O;
	
	unless ($sub) {
		print $Config{'startperl'}, "\n";
		print <<'EOM';
	use HTML::Merge::Engine;
	use HTML::Merge::Error;
	no strict;
	sub getvar ($) {
		$vars{shift()};
	}
	sub setvar ($$) {
		$vars{$_[0]} = $_[1];
	}
	sub incvar ($$) {
		$vars{$_[0]} += $_[1];
	}
	sub getfield ($;$) {
		my ($field, $engine) = @_;
		$engines{$engine}->Var($field);
	}
	sub merge ($) {
		my $code = shift;
		require HTML::Merge::Compile;
		my $text;
		eval { $text = HTML::Merge::Compile::Compile($code, __FILE__); };
		HTML::Merge::Error::HandleError('ERROR', $@) if $@;
		eval $text;
		HTML::Merge::Error::HandleError('ERROR', $@) if $@;
	}
	sub dbh () {
		$engines{""}->{'dbh'};
	}
	sub register ($) {
		push(@HTML::Merge::cleanups, shift);
	}

	if (tied(%engines)) {
		undef %engines;
		untie %engines;
	}

	tie %engines, HTML::Merge::Engine;
	use CGI qw/:standard/;
	@keys = param();
	%vars = ();
	foreach (@keys) {
		$vars{$_} = param($_);
	}
=line
	$tmp = HTML::Merge::Compile::CgiParse();
	foreach (keys(%$tmp))
	{
		print "$_\t:\t",$tmp->{$_},"\n";
	}

	%vars = %$tmp; 
=cut
	unless ($HTML::Merge::Ini::TEMPLATE_CACHE) {
	
EOM
		print "\t\trequire '$HTML::Merge::config';\n\t}\n";
	}

	eval {	
		print &Compile($text, $file);
	};
	my $code = $@;
	
	unless ($sub) {
		print <<'EOM';
	HTML::Merge::Engine::DumpSuffix;
	untie %engines;

	1;
EOM
	}

	select $prev;
	close(O);
	die $code if $code;
	chmod 0755, $out;
	
}

sub Syntax {
	my $self = shift;
	&DB::Syntax($self);
}


package DB;

sub Syntax {
	my $self = shift;
	my $step = 0;
	my $sub;
	my $pkg = ref($self);
	for (;;) {
		$step++;
		my @c = caller($step);
		$sub = $c[3];
		last if $sub =~ s/^(.*)::Do// && UNIVERSAL::isa($self, $1);
	} 
	$self->Die("Syntax error on $sub: $DB::args[2]");
}


package HTML::Merge::Ext;

sub Macro {
	my $text = shift;
	$text =~ s/(?<!\\)\$(\d+)/\000$_[$1 - 1]\000/g;

	$HTML::Merge::Ext::COMPILER->Macro($text);
	return "";
}

1;