#!/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 : '//' comment_line { "## $item[3]"} | m{/LET}i assignment_line end_comment{ "$item[3]; $item[4]"} | m{/BEG}i 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 answer_line end_comment{ "\nTEXT(\"\$BR\$BR\",ans_rule(30),\"\$BR\");\nANS( CAPA_ans( $item[3] ) ); $item[4]"} | m{/IMP}i import_line end_comment { "CAPA_import( $item[3] ); $item[4]"} | m{/HIN}i hint_line { # no comments permitted in hints "CAPA_hint( \"$item[3]\"); $item[4]"; } | m{/EXP}i explanation_line { # no comments permitted in explanations "CAPA_explanation( \"$item[3]\"); $item[4]"; } | m{/END}i 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"; } | 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 : | '(' 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' '(' 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;} | | 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 : 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 '=' option_value {"'sig' => \'$item[4]\'" } | /TOL/i '=' expression ('%')(?) { if (${$item[5]}[0]) { $return = "'reltol' => $item[4]"; } else { $return = "'tol' => $item[4]"; } } | /str/i '=' option_value {" 'str' => \'$item[3] \'"} | option_name '=' expression {" '$item[1]' => $item[3] "} option_name : identifier option_value : /[^,\)]*/ expression : {unshift(@capa2PG::STRING_FLAG_STACK, 0 ); } | 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] " } | '-' 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"; } expression_iter : term extend_expression { "$item[1] $item[2]"; } extend_expression : add_op expression_iter { $item[1] =~ s/\+/\./ if $capa2PG::STRING_FLAG_STACK[0]; "$item[1] $item[3]" } | {''} term : factor extend_term { "$item[1] $item[3]"; } | quote extend_term : mult_op term { "$item[1] $item[3]"} | {''} factor : function { "$item[1]" } | variable { $item[1] } | '(' 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 expression {"$item[1] $item[3]" } | {''} comparison_operator : '==' | '!=' | '>=' | '<=' | '>' | '<' function : function_identifier '(' ')' { "$item[1]$item[2]$item[3]" } | function_identifier '(' 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 : | 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 : | 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*/ /[^"]*/ '"' { $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') ('-'|'+')(?) 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 = ; # 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