#!/usr/math/bin/perl -w

# READ CAPA file


package capa2PG;

use vars qw( $STRING_FLAG  $STRING_FLAG_STACK @ISA);
use Exporter;
@ISA  = qw(Exporter);
@EXPORT   = qw(parse_CAPA_file parse_CAPA_aux_file);

use strict;
use Parse::RecDescent;

## begin Timing code
#use Benchmark;
#my $beginTime = new Benchmark;
## end Timing code

sub flatten {
	my (@in) = @_;
	my @out = ();
	my $elem;
	foreach $elem (@in) {
		if (ref($elem) eq 'ARRAY') {
		      push(@out, flatten(@$elem) );
		} else {
		    push(@out, $elem);
		}
	}
	@out;
}

sub printtree
{
	print "    " x $_[0];
	if (  defined($_[1])  )  {
			
		if (ref($_[1]) ) {
			printtree($_[0]+1,@{$_[1]});
		} else {
		    print("$_[1]:\n");
		}
		foreach ( @_[2..$#_] )
		{
			if (ref($_)) { printtree($_[0]+1,@$_); }
			else	     { print "    " x $_[0], "$_\n" }
		}
		print "\n";
	}

}


sub DISPLAY {
	$capa2PG::OUTPUTstring .= join("\n", @_) . "\n";
}

sub ERROR  {
	$capa2PG::ERRORstring .= join("\n", @_) . "\n";
}

# 
# $::RD_AUTOACTION = q
#  	  { print "--", join("<|>",@item), "\n"; };
#$::RD_HINT=1;

$::RD_AUTOACTION = q{ "@item[1..$#item]"};
my $Grammar=<<'EOF';


line	: '//' <commit> comment_line {     
	 	"## $item[3]"}
	| m{/LET}i <commit> assignment_line  end_comment{  
	 	"$item[3];  $item[4]"} 
	| m{/BEG}i <commit> begin_line  end_comment {      #begins with /BEGIN
		qq{
		ENDDOCUMENT();\n\n\n
		 ####################################################\n
		 DOCUMENT();\n
		 loadMacros(\n"PG.pl",\n 
		             "PGbasicmacros.pl",\n
		             "PGauxiliaryFunctions.pl",
					 "PGchoicemacros.pl",\n
                     "PGanswermacros.pl",\n
					 "PGgraphmacros.pl",\n
					 "PG_CAPAmacros.pl"\n
		 );
		 
		 TEXT(beginproblem());\n
		 $item[3]; $item[4]\n
		}
	   }
	| m{/ANS}i <commit> answer_line end_comment{
		"\nTEXT(\"\$BR\$BR\",ans_rule(30),\"\$BR\");\nANS( CAPA_ans( $item[3] ) ); $item[4]"}
	| m{/IMP}i <commit> import_line end_comment {
		"CAPA_import( $item[3] );   $item[4]"}
	| m{/HIN}i <commit> hint_line  {          # no comments permitted in hints
		"CAPA_hint( \"$item[3]\");   $item[4]";  }
	| m{/EXP}i  <commit> explanation_line {   # no comments permitted in explanations
		"CAPA_explanation( \"$item[3]\");   $item[4]";  }
	| m{/END}i  <commit> end_line  {
		"\nENDDOCUMENT();\n__END__\n"}
	| text_line         
	    {                #anything else -- no end comments permitted in text.
			"TEXT(CAPA_EV (<<'END_OF_TEXT'));\n$item[1]\nEND_OF_TEXT\n";
		 }
                 
	| <error>


comment_line    :  /.*/ {
	"$item[1]";
	}
end_comment         : '//' /.*/ {
							"# $item[2]";
							}
				  | {""}

begin_line      :    identifier_to_be_defined '=' expression {
	"@item[1..3]";}
import_line     :   '"' filename '"'
hint_line       :   /.*/ 
explanation_line :  /.*/

answer_line     :   <rulevar:$out><reject>
                  | '(' expression (':')(?) format(?) (',')(?) option_list(?) ')' {
 					$out = "$item[2]";
 					$out .= ", 'format' => ${$item[4]}[0]" if defined(${$item[4]}[0]);
 					$out .= " ${$item[5]}[0] ";
                      $out  .= "${$item[6]}[0]"}                     
                     
                     
                     
end_line        :   /.*/

text_line       : m{[^/]*}    command text_line {
	             "$item[1]$item[2]$item[3]";
		  }
                  | m{[^/]*} {
	             "$item[1]";
		  }
		
command         :   map_command | display_command | '/'
display_command : '/' 'DIS'   display_argument  {
                           if ( $text =~ /^\s/) {
                           		$return = "$item[3] "; # kludge -- put an extra space in if there is a space after the display command
                           } else {
                           		$return = "$item[3]"
                           }
                            
                     }

#display_argument   : '('  /[^\)]*/  ')' {"@item"}

 display_argument :  '(' function ')' {
 	                "\\{ $item[2]  \\}" ; # this keeps us from mistaking a function for an undefined variable and getting an error message
 	       			}
 		           | '(' variable ')' {# no evaluation needed but we need to worry about spacing.
 		             "\\{ $item[2] \\}"} 
                   | '(' expression (':')(?) format(?) ')' {
 	           
 	            ${$item[4]}[0] ? "\\{  spf( $item[2] , ${$item[4]}[0] ) \\}" :   "\\{  $item[2]  \\}" ;
 	       
 		    }
                  
map_command     : '/' 'MAP' <commit> '(' expression ';' variable_list ';' expression_list ')' {
	            "\\{CAPA_map(    $item[5]  , [ $item[7] ] , [  $item[9] ]  )\\}\n";
		    }

variable_list   :  list[rule => 'variable', sep => ','] { $item[1] =~ s/\$(\w+)/'$1'/g; $item[1]; }

assignment_line :    { $capa2PG::STRING_FLAG = 0;} <reject>
                    |<rulevar:$out> <reject>
	            |  identifier_to_be_defined '=' expression 
                       {
		
		        $out = "$item[1] $item[2] $item[3]";
		        #print "line is $out\nSTRING_FLAG = $capa2PG::STRING_FLAG\n";
			if ($capa2PG::STRING_FLAG) {
		           #print "defining $capa2PG::CURRENT_IDENTIFIER_TO_BE_DEFINED\n";
			       $capa2PG::IDENTIFIERS{$capa2PG::CURRENT_IDENTIFIER_TO_BE_DEFINED}++;
			}
		      $out;	
		     }
		       


list             :  <matchrule:$arg{rule}> extend_list[%arg]
 				{ $return = "$item[1]$item[2]" }
extend_list      :  /$arg{sep}/ list[@arg] {"$item[1] $item[2]"}
                   | {""}


expression_list  : list[rule => 'expression', sep => ','] {
	            "$item[1]"
		    }
option_list      : list[rule => 'option', sep => ','] {
 	            "$item[1]"
 		    }	
#option_list      :/[^\)]*/
option           : /SIG/i <commit> '=' option_value {"'sig' => \'$item[4]\'" }
                  | /TOL/i <commit> '=' expression ('%')(?) {
	                   if (${$item[5]}[0]) {
	                         $return = "'reltol' => $item[4]";
	                   } else {
	                   	     $return = "'tol' => $item[4]";
	                   }
	                }
	               | /str/i  <commit>     '=' option_value {" 'str' => \'$item[3] \'"}
                   | option_name '=' expression {" '$item[1]' => $item[3] "}
option_name      : identifier
option_value     : /[^,\)]*/
expression       : {unshift(@capa2PG::STRING_FLAG_STACK, 0 );  } <reject>
                   | expression_iter {$capa2PG::STRING_FLAG = shift (@capa2PG::STRING_FLAG_STACK);
                      #print "stack depth = ", scalar(@capa2PG::STRING_FLAG_STACK), "\n";
                      #print "string is $item[1]\nSTRING_FLAG is $capa2PG::STRING_FLAG\n";
                      # $item[1] =~ s/\+/ \. /g if $capa2PG::STRING_FLAG; 
                      "$item[1] "
                      }	
                    | '-' <commit>  expression_iter {$capa2PG::STRING_FLAG = shift (@capa2PG::STRING_FLAG_STACK);
                      #print "stack depth = ", scalar(@capa2PG::STRING_FLAG_STACK), "\n";
                      #print "string is $item[1]\nSTRING_FLAG is $capa2PG::STRING_FLAG\n";
                      # $item[1] =~ s/\+/ \. /g if $capa2PG::STRING_FLAG; 
                      "$item[1] $item[3]"
                      }	
                    | {unshift(@capa2PG::STRING_FLAG_STACK);
                       #print "no match in expression\n";
                       #print "stack depth = ", scalar(@capa2PG::STRING_FLAG_STACK), "\n"; 
                       } <reject>     
expression_iter  : term extend_expression {
	             "$item[1] $item[2]";
		     }
                 
extend_expression : add_op <commit> expression_iter 	{ 
                           $item[1] =~ s/\+/\./ if $capa2PG::STRING_FLAG_STACK[0];
                            "$item[1] $item[3]"
                            }
                   | {''}		  

term            :  factor <commit> extend_term   {
	              "$item[1] $item[3]";
		     }
                  | quote
		  
extend_term     :  mult_op <commit> term { "$item[1] $item[3]"}	
                  | {''}	 
factor          :   function {
					 	"$item[1]"
					    }
			     |  variable  {
					 	$item[1]
					    }
			     |  '(' <commit> expression comparison_extension')'{
			            $capa2PG::STRING_FLAG_STACK[0]++ if $capa2PG::STRING_FLAG;  # if the expression is a string variable
					 	"$item[1] $item[3] $item[4]$item[5]"
					    }
				 |  number {
					 	$item[1]
					    }
comparison_extension : comparison_operator <commit> expression {"$item[1] $item[3]" }
                      | {''}
comparison_operator  :  '==' | '!=' | '>=' | '<=' | '>' | '<' 
                    
function         : function_identifier '('   ')' {
	               "$item[1]$item[2]$item[3]"
		       }
                   | function_identifier '(' <commit> expression_list ')'  {
                    $capa2PG::STRING_FLAG_STACK[0]++ if $capa2PG::STRING_FLAG;  # if the expression is a string variable
	               "$item[1]$item[2] $item[4]$item[5]"
		       }

function_identifier : 'web' {'CAPA_web'}
                     | 'html' {'CAPA_html'}
                     | 'tex' {'CAPA_tex'}
                     | identifier

identifier_to_be_defined       :  <rulevar: $var_name>
                 |   identifier  {
	                        $var_name = "\$$item[1]";
                                 $capa2PG::CURRENT_IDENTIFIER_TO_BE_DEFINED = $var_name;
				$capa2PG::IDENTIFIERS{$capa2PG::CURRENT_IDENTIFIER_TO_BE_DEFINED} = 0;
		                 
			         $var_name
			    
			    }
variable        :  <rulevar: $var_name>
                 |   identifier  {
	                        $var_name = "\$$item[1]";
                                capa2PG::ERROR( "###Error: $var_name not defined in this file" )
                                  unless defined ( $capa2PG::IDENTIFIERS{$var_name} ) ;
				 $capa2PG::STRING_FLAG_STACK[0]++ if $capa2PG::IDENTIFIERS{$var_name};  # if the identifier is a string variable
				 
			     $return = "${var_name}";
			    
			    }
quote            :  /"\s*/ <commit> /[^"]*/  '"'  {  
                     $item[3] =~ s/\'/~~'/g;  #protect single quotes
                    # $item[3] = " " if $item[3] =~ /^\s*$/;  # kludge to make sure spaces survive in quotes. Changing separator would be better.
                     $item[1] =~s/"/'/;   # replace the first quote with a single quote, spacing is preserved.
                     $capa2PG::STRING_FLAG_STACK[0]++; "$item[1]$item[3]'"}
                 
format           : /(\d+)(\w)/ {"\"%0.$1" .lc($2) . "\""}                           
number           : /\-?\d+\.?\d*/ exponent(?) {
	                $item[1].join("",@{$item[2]})
		        }
                  | /\-?\.\d+/ exponent(?)    {
	                $item[1].join("", @{$item[2]})
		        }
		  
integer          :  /\d+/                        
exponent         : ('E' | 'e') <commit> ('-'|'+')(?) integer {
	              $item[1].join("",@{$item[3]}).$item[4]
		      }
identifier      :  /[A-Za-z][A-Za-z0-9_]*/ 

mult_op         : '*' | '/' ...!'/'   # this keeps us from grabbing // (comment) by mistake                             
add_op          : '+' | '-'     
                      

filename        :  /[A-Za-z0-9\.\/\#]+/

EOF


my $parser = new Parse::RecDescent $Grammar  or  die "invalid grammar";

sub parse_CAPA_file {
		my $array_ref = shift;    # takes an array ref for input -- check this below
		return "\nparse_CAPA_file requires an array reference for input and returns a string\n\n" 
		       unless ref($array_ref) =~ /ARRAY/;
    $capa2PG::STRING_FLAG = 0;
	@capa2PG::STRING_FLAG_STACK = ();
	$capa2PG::OUTPUTstring = "";
	$capa2PG::ERRORstring = "";
	my $counter =0;
	
	DISPLAY  qq{
	 DOCUMENT();
	 loadMacros( "PG.pl", 
	             "PGbasicmacros.pl",
	             "PGauxiliaryFunctions.pl",
	             "PGchoicemacros.pl",
	             "PGanswermacros.pl",
	             "PGgraphmacros.pl",
	             "PG_CAPAmacros.pl"
	 );
	 
	 TEXT(beginproblem());
	 \$showPartialCorrectAnswers =1;
	 
	};
	my $line;
	my $extend_line = "";
	foreach $line (@$array_ref) {
		chomp($line);
		# continue lines which end in \
			if ($line =~ /\\(\n*)$/)  { # continue lines which end in \
			    $line =~ s/\\$/ /;
				$extend_line .=$line;
				next;
			} else {
				if ($extend_line) { # if extend_line is non-empty add line and process
					$line = $extend_line . $line;
					$extend_line = "";   # reset extend _line
				}
			} 
		$line =~ s/\$\$/\{\}\/*\/*\{\}/g;
		$line =~ s/\$/\{\}\/*\{\}/g;
		$line = $parser->line($line);
# 		$line =~ s|src\s*=\s*/teacher/capalibrary/Graphics/|src = \${Global::CAPA_Graphics_URL}|;  # trying to guess the graphics url and directory addresses
# 		$line =~ s|/teacher/capalibrary/Graphics/|\${Global::CAPA_GraphicsDirectory}|;
# 		$line =~ s|/teacher/capalibrary/Tools/|\${Global::CAPA_Tools}|;
# 		$line =~ s|/teacher/capalibrary/MCTools/|\${Global::CAPA_MCTools}|;
		$line =~ s|src\s*=\s*/teacher/capalibrary/Graphics/|src = \${CAPA_Graphics_URL}|;  # trying to guess the graphics url and directory addresses
		$line =~ s|/teacher/capalibrary/Graphics/|\${CAPA_GraphicsDirectory}|;
		$line =~ s|/teacher/capalibrary/Tools/|\${CAPA_Tools}|;
		$line =~ s|/teacher/capalibrary/MCTools/|\${CAPA_MCTools}|;
		#print "\nSTRING_FLAG = $capa2PG::STRING_FLAG\n";
		DISPLAY($line);
	}
	
	DISPLAY("ENDDOCUMENT();\n");
	#DISPLAY("1;  # required for auxilliary files");
	
	DISPLAY( "#####################\n");
	DISPLAY( $capa2PG::ERRORstring);
	DISPLAY( "#####################\n");
	
	
	$capa2PG::OUTPUTstring =~ s/\nEND_OF_TEXT\n[\s\n\r\#]*TEXT\(CAPA_EV \(<<'END\_OF\_TEXT'\)\);\n/\n/gs;
	
	$capa2PG::OUTPUTstring;
}

sub parse_CAPA_aux_file {
	my $array_ref = shift;    # takes an array ref for input -- check this below
		return "\nparse_CAPA_aux_file requires an array reference for input and returns a string\n\n" 
		       unless ref($array_ref) =~ /ARRAY/;
    $capa2PG::STRING_FLAG = 0;
	@capa2PG::STRING_FLAG_STACK = ();
	$capa2PG::OUTPUTstring = "";
	$capa2PG::ERRORstring = "";
	my $counter =0;
	
	my $line;
	my $extend_line = "";
	foreach $line (@$array_ref) {
		# continue lines which end in \
			if ($line =~ /\\(\n*)$/)  { # continue lines which end in \
			    $line =~ s/\\$/ /;
				$extend_line .=$line;
				next;
			} else {
				if ($extend_line) { # if extend_line is non-empty add line and process
					$line = $extend_line . $line;
					$extend_line = "";   # reset extend _line
				}
			} 
		$line =~ s/\$\$/\{\}\/*\/*\{\}/g;
		$line =~ s/\$/\{\}\/*\{\}/g;
		$line = $parser->line($line);
# 		$line =~ s|src\s*=\s*/teacher/capalibrary/Graphics/|src = \${Global::CAPA_Graphics_URL}|;  # trying to guess the graphics url and directory addresses
# 		$line =~ s|/teacher/capalibrary/Graphics/|\${Global::CAPA_GraphicsDirectory}|;
# 		$line =~ s|/teacher/capalibrary/Tools/|\${Global::CAPA_Tools}|;
# 		$line =~ s|/teacher/capalibrary/MCTools/|\${Global::CAPA_MCTools}|;
		$line =~ s|src\s*=\s*/teacher/capalibrary/Graphics/|src = \${CAPA_Graphics_URL}|;  # trying to guess the graphics url and directory addresses
		$line =~ s|/teacher/capalibrary/Graphics/|\${CAPA_GraphicsDirectory}|;
		$line =~ s|/teacher/capalibrary/Tools/|\${CAPA_Tools}|;
		$line =~ s|/teacher/capalibrary/MCTools/|\${CAPA_MCTools}|;
		#print "\nSTRING_FLAG = $::STRING_FLAG\n";
		DISPLAY($line);
	}
	
	
	DISPLAY( "#####################\n");
	DISPLAY( $capa2PG::ERRORstring);
	DISPLAY( "#####################\n");
	
	DISPLAY("1;   # required for auxiliary files");
	

	
	$capa2PG::OUTPUTstring =~ s/\nEND_OF_TEXT\n[\s\n\r\#]*TEXT\(CAPA_EV \(<<'END\_OF\_TEXT'\)\);\n/\n/gs;
	
	$capa2PG::OUTPUTstring;
}


# my $ARGV;
# my @file_lines;
# @ARGV = ('-') unless @ARGV;
# while ($ARGV = shift) {
#     print "#   READING FROM $ARGV\n\n";
# 	
#  	open(ARGV, "<$ARGV") || die " Can't read file $ARGV ";
#  	@file_lines = <ARGV>;
#  	close(ARGV);
#  	print parse_CAPA_file(\@file_lines);
# }

## begin Timing code
#my $endTime = new Benchmark;
#print "\n#################################################\n## Processing time = ", timestr( timediff($endTime,$beginTime) ), 
#      "\n#################################################\n";
## end Timing code

#exit;
1;
# __DATA__
# /Let   test = 45 -67*87 + tex("testing" + "test",2+5,"one")
# /let  test2 = 45 + 67 *9 
# /let  test3 = test + test2 + test

