package TemplateParser; use strict; #----------------------------------------------------------------------------- # This package parses a template file in the format explained below, and # translates it into Perl code. See jeeves for where this package fits # into the scheme of things. # The template file recognizes the following directives ... # (keywords are case insensitive) # @OPENFILE [options] - closes the previous output file, # the new file. # Options: # -append - open the file in append mode # -no_overwrite - do not overwrite the file if it already exists. # This is useful if you want to generate the file only once. # -only_if_different - puts all the output into a temp file, does a # diff with the given file, and overwrites it if the two # files differ - useful in a make environment, where you # don't want to unnecessarily touch the file if the contents # are the same, to preserve timestamps # # @PERL - Inserts the perl code in the output file untranslated # @FOREACH [perl condition code] - iterates thru the array @var, using # the iterator variable $var_i. The iteration works # wherever the condition is true. # # @END - terminates the loop # @// - comment line, not reproduced in the intermediate perl file # All other lines in the template are left essentially untranslated. #----------------------------------------------------------------------------- sub parse { # Args : template file, intermediate perl file my ($pkg,$template_file, $inter_file) = @_; unless (open (T, $template_file)) { warn "$template_file : $@"; return 1; } open (I, "> $inter_file") || die "Error opening intermediate file $inter_file : $@"; emit_opening_stmts($template_file); my $line; while (defined($line = )) { if ($line !~ /^\s*\@/) { # Is it a command? emit_text($line); next; } if ($line =~ /^\s*\@OPENFILE\s*(.*)\s*$/i) { emit_open_file ($1); } elsif ($line =~ /^\s*\@FOREACH\s*(\w*)\s*(.*)\s*/i) { emit_loop_begin ($1,$2); } elsif ($line =~ /^\s*\@END/i) { emit_loop_end(); } elsif ($line =~ /^\s*\@PERL(.*)/i) { emit_perl("$1\n"); }; } emit_closing_stmts(); close(I); return 0; } # All pieces of output code are within a "here" document terminated # by _EOC_ # #---------------------------------------------------------------------- # emit_opening_stmts # ==> emit ("Convert ROOT's properties to global variable names") # sub emit_opening_stmts { my $template_file = shift; emit("# Created automatically from $template_file"); emit(<<'_EOC_'); use Ast; use JeevesUtil; $tmp_file = "jeeves.tmp"; sub open_file; if (! (defined ($ROOT) && $ROOT)) { die "ROOT not defined"; } $file = "> -"; open (F, $file) || die $@; $code = ""; $ROOT->visit(); _EOC_ } #------------------------------------------------------------------------ # emit_open_file # ==> emit ("Close the previous file, and open the new filename for output # sub emit_open_file { my $file = shift; my $no_overwrite = ($file =~ s/-no_overwrite//gi) ? 1 : 0; my $append = ($file =~ s/-append//gi) ? 1 : 0; my $only_if_different = ($file =~ s/-only_if_different//gi) ? 1 : 0; $file =~ s/\s*//g; emit (<<"_EOC_"); # Line $. open_file(\"$file\", $no_overwrite, $only_if_different, $append); _EOC_ } #---------------------------------------------------------------------- # emit_loop_begin # ==> emit ("manufacture an iterator name, and visit each element in # that array") # The best way to understand this code is to execute the schema compiler # and look at the intermediate perl code. # sub emit_loop_begin { my $l_name = shift; # Name of the list variable my $condition = shift; my $l_name_i = $l_name . "_i"; emit (<<"_EOC_"); # Line $. foreach \$$l_name_i (\@\${$l_name}) { \$$l_name_i->visit (); _EOC_ if ($condition) { emit ("next if (! ($condition));\n"); } } #---------------------------------------------------------------------- sub emit_loop_end { emit(<<"_EOC_"); #Line $. Ast->bye(); } _EOC_ } #---------------------------------------------------------------------- sub emit_perl { emit($_[0]); } #---------------------------------------------------------------------- sub emit_text { chomp $_[0]; # Escape quotes in the text $_[0] =~ s/"/\\"/g; $_[0] =~ s/'/\\'/g; emit(<<"_EOC_"); output("$_[0]\\n"); _EOC_ } #---------------------------------------------------------------------- sub emit_closing_stmts { emit(<<'_EOC_'); Ast->bye(); close(F); unlink ($tmp_file); sub open_file { my ($a_file, $a_nooverwrite, $a_only_if_different, $a_append) = @_; #First deal with the file previously opened close (F); if ($only_if_different) { if (JeevesUtil::compare ($orig_file, $curr_file) != 0) { rename ($curr_file, $orig_file) || die "Error renaming $curr_file to $orig_file"; } } #Now for the new file ... $curr_file = $orig_file = $a_file; $only_if_different = ($a_only_if_different && (-f $curr_file)) ? 1 : 0; $no_overwrite = ($a_nooverwrite && (-f $curr_file)) ? 1 : 0; $mode = ($a_append) ? ">>" : ">"; if ($only_if_different) { unlink ($tmp_file); $curr_file = $tmp_file; } if (! $no_overwrite) { open (F, "$mode $curr_file") || die "could not open $curr_file"; } } sub output { print F @_ if (! $no_overwrite) } 1; _EOC_ } #---------------------------------------------------------------------- sub emit { print I $_[0]; } 1; # returns 1 if successfully compiled