#!/usr/bin/env perl #============================================================================== # # Name: start_sim # # Synopsis: start_sim -n -i / # # Description: Run any simulation from any path # # Assumptions: 1) Must be a Trick installed user # # Examples: start_sim SIM_cannon RUN_test # # Created By: Warwick Woodard, L-3 Communications 05/01/2008 # #============================================================================== use lib $ENV{"TRICK_HOME"} . "/bin/pm" ; use gte ; #use strict ; require $ENV{"TRICK_HOME"} . "/bin/pm/XML/Parser.pm" ; use XML::Simple ; my @element_stack; my $record_index; my $fh; my $DATA_PATH; my $INCLUDE_PATH; my $usage=" Name: start_sim - run any simulation from any path Additional options from S_main_.exe (above) are also available for use with start_sim. Options specific to start_sim command: (Note: start_sim can run without options) -s, -sim, -name name of trick directory system that holds the simulation -i, -input simulation input file to use -nexiom, -nexcsims parse a NExIOM input file and generate a nexcsims.d data file, then auto load nexcsims.d after standard input file is read -user_home, -u overwrite default value for the location of sims (full path) -freq <#.##> overwrite data recording cycle time (>= 0.01 sec) -ddd, -debug start simulation using the data display debugger (DDD) -gdb start simulation using the GNU debugger -echo send start_sim script print statements to terminal -h, -help, --help display this help and exit Usage: start_sim [-s ] [-i ] [option(s)...] Examples: % start_sim -s SIM_cannon -i RUN_grav/input % start_sim % start_sim -O \$HOME/my_log_files/ % start_sim -nexiom nexiom_input.xml " ; # Local Variables my $name=""; # Name of Trick directory system that holds simulation my $input=""; # Simulation Input File my $use_input=1; # Input file required for S_MAIN_*.exe my $nexiom_format=0; # Input file is in XML format, create XML output my $nexiom_input=""; # Filename of the nexiom input parameters (XML format) my $nexiom_output=""; # Filename of the nexiom output (Datatable format) my $freq=0.0; # Overwrite data recording rate (>= 0.01 sec) my $user_home=""; # Overwrite default location of $TRICK_USER_HOME my $echo_ON=0; # Print the generated start command to screen my $help=0; # Print usage text to screen my $DDD=0; # Start simulation in ddd Flag my $GDB=0; # Start simulation in gdb Flag my $options=""; # Additional options to pass along to S_MAIN_*.exe my $default_host_cpu="Linux_3.4_234_x86_64";# Given in case TRICK_HOST_CPU is undef my $kernel_name; open $fh, "uname -s |"; { local $/; $kernel_name = trim(<$fh>); # remove whitespace } close $fh; my $redirect=""; if ( substr($kernel_name,0,4) eq "IRIX" ) { $redirect=" > /dev/null "; # Redirect outputs away from the screen } # IMPORTANT: Do not create options that could be used by S_MAIN.exe while ( @ARGV > 0 ) { my $argc = $ARGV[0]; # Copy current argument my $argc2 = $ARGV[1]; if (( $argc eq "-name" ) || ( $argc eq "-sim" ) || ( $argc eq "-s" )) { shift; $name=$argc2; } elsif (( $argc eq "-input" ) || ( $argc eq "-i" ) || ( $argc eq "-run" )) { shift; $input=$argc2; } elsif (( $argc eq "-frequency" ) || ( $argc eq "-freq" ) || ( $argc eq "-f" )) { shift; $freq=$argc2; } elsif (( $argc eq "-user_home" ) || ( $argc eq "-user" ) || ( $argc eq "-u" )) { shift; $user_home=$argc2; } elsif (( $argc eq "-debug" ) || ( $argc eq "-ddd" )) { $DDD=1; } elsif (( $argc eq "-gdb" )) { $GDB=1; } elsif (( $argc eq "-echo" ) || ( $argc eq "-print" )) { $echo_ON=1; } elsif (( $argc eq "-help" ) || ( $argc eq "-h" ) || ( $argc eq "help" ) || ( $argc eq "-x" )) { $help=1; $options = $options." -help"; } elsif (( $argc eq "-nexiom" ) || ( $argc eq "-nexcsims" )) { $nexiom_format=1; shift; $nexiom_input=$argc2; } else { if (( $argc eq "sie" ) || ( $argc eq "trick_version" ) || ( $argc eq "-V" ) || ( $argc eq "--version" )) { # Don't run sim; generate files or print info to screen. $use_input = 0; # Save option. $options = $options." ".$argc; } elsif ( substr($argc,0,1) eq "-" ) { # Add two backslashes to support Debug argument parsing. # Otherwise some arguments are saved as internal ddd/gdb # commands and not passed along to S_MAIN.exe $options = $options." ".$argc."\\\\"; } else { # Leave argument as is and append to previous option(s). $options = $options." ".$argc; } } shift; } # Executable name is a product of host_cpu description. my $S_MAIN_EXE; if ( defined($ENV{"TRICK_HOST_CPU"}) ) { # Set "S_MAIN_EXE" equal to "S_main_" + the environment variable TRICK_HOST_CPU $S_MAIN_EXE = "S_main_".$ENV{"TRICK_HOST_CPU"} .".exe"; # concatenate strings } else { # If TRICK_HOST_CPU is undef, then "S_MAIN_EXE" is equal to "S_main_" plus # local variable default_host_cpu (set above). $S_MAIN_EXE = "S_main_".$default_host_cpu .".exe"; # concatenate strings } if ( $echo_ON == 1 ) { print "debug: setting S_MAIN_EXE equal to \"$S_MAIN_EXE\"\n"; } # If $user_home is not null, copy value if ( $user_home ne "" ) { # If $user_home input is not null, test if path exists if ( ! -d $user_home ) { # An invalid sim path was provided; clear variable $user_home=""; } } my $userid; open $fh, "whoami |"; { local $/; $userid = trim(<$fh>); # remove whitespace } close $fh; my $trick_sims; if ( $user_home ne "" ) { # Set "trick_sims" equal to the variable $user_home. $trick_sims = $user_home; } elsif ( defined($ENV{"TRICK_USER_HOME"}) ) { # If $user_home not set, set "trick_sims" equal to the env var TRICK_USER_HOME. $trick_sims = $ENV{"TRICK_USER_HOME"}; } elsif ( defined($ENV{"HOME"}) ) { # If TRICK_USER_HOME is undef, then set "trick_sims" equal to $HOME/trick_sims. $trick_sims = $ENV{"HOME"}."/trick_sims"; # concatenate strings } else { # If HOME is undefined then default "trick_sims" to /users//trick_sims. $trick_sims = "/users/".$userid."/trick_sims"; # concatenate strings } if ( $echo_ON == 1 ) { print "debug: setting trick_sims equal to \"$trick_sims\"\n"; } my $pwd_; if ( defined($ENV{"PWD"}) ) { # Set "pwd_" equal to the envirnment variable PWD. $pwd_ = $ENV{"PWD"}; } elsif ( defined($ENV{"cwd"}) ) { # If PWD is undef, then set "pwd_" equal to the envirnment variable cwd. $pwd_ = $ENV{"cwd"}; } else { # If cwd is undefined, then execute `pwd`. open $fh, "pwd |"; { local $/; $pwd_ = trim(<$fh>); # remove whitespace } close $fh; } # which Simulation to use # Possible Scenarios: # 1. SIM_name was provided by user # 2. SIM_name was not entered but current path is within a simulation directory # 3. SIM_name was not entered & current path is not within any simulation dir my $sim_="SIM"; if ( $name ne "" ) { # If $name is not null, copy value $sim_ = $name; if ( substr($name,0,1) eq "/" ) { # If the full path of the simulation directory was entered, # then separate into two parts my $last_slash = rindex($name, "\/"); if ( -e substr($name,0,$last_slash) ) { # if path exists, copy all chars prior to the last forward slash $trick_sims = substr($name,0,$last_slash); } $sim_=substr($name,($last_slash+1)); # copy all chars after the last forward slash } } elsif ( ($nexiom_format == 1) && ($nexiom_input ne "") ) { # If $nexiom_input is not null, parse file $sim_ = process_nexiom_input( $nexiom_input, "name"); if ( substr($sim_,0,1) eq "/" ) { # If the full path of the simulation directory was entered, # then separate into two parts my $last_slash = rindex($sim_, "\/"); if ( -e substr($sim_,0,$last_slash) ) { # if path exists, copy all chars prior to the last forward slash $trick_sims = substr($sim_,0,$last_slash); } $sim_=substr($sim_,($last_slash+1)); # copy all chars after the last forward slash } } else { # $name is null, search the current path to determine if # this script was executed within a simulation directory. # If not, default to the simulation directory with the # most recent time stamp. if ( `echo $pwd_ | grep $sim_ $redirect` ) { # Script was executed within a Trick simulation directory my $last_slash = rindex($pwd_, $sim_) - 1; $trick_sims = substr($pwd_,0,$last_slash); # Copy section of path PRIOR to SIM_name $sim_=substr($pwd_,($last_slash+1)); # Copy section of path AFTER trick_sims # Copy portion of SIM_name PRIOR to first slash (if any) # This would imply that the path of execution was nested # down past the simulation directory path if ( index($sim_, "\/") != -1 ) { my $new_slash = index($sim_, "\/"); $sim_ = substr($sim_,0,$new_slash); } } else { # Script was executed outside of a Trick simulation directory # List, then sort (comma delimited) all SIM_names by mod time open $fh, "ls -vtmBd ".$trick_sims."/".$sim_."* ".$redirect." |"; { local $/; $sim_ = trim(<$fh>); # remove whitespace } close $fh; # Copy the single SIM_name PRIOR to the first comma (if any) if ( index($sim_, ",") != -1 ) { my $first_comma = index($sim_, ","); $sim_ = substr($sim_,0,$first_comma); } # Results show full path; # strip off everything up to and including the last slash my $last_slash = rindex($sim_, "\/"); $sim_=substr($sim_,($last_slash+1)); # copy all chars after the last forward slash } } my $NAME = $trick_sims."/".$sim_ ; if ( $echo_ON == 1 ) { print "debug: setting NAME equal to \"$NAME\"\n"; } if ( ! -e $NAME ) { print "\e[31mError:\e[00m SIM directory... \n\"$NAME\" \n...does not exists.\n"; if ( $nexiom_format == 1 ) { print " Please overwrite NExIOM value by adding \"-s \"\n"; print " (e.g. \"start_sim -nexiom -s \" )\n"; } print " \e[34mPlease set to one of the following:\e[00m\n"; system("ls -vtmBd ".$trick_sims."/*SIM*"); print "Type \"start_sim -help\" for more info.\n"; exit } # where to find S_default.dat $DATA_PATH=$NAME; # where to look for input file's #includes $INCLUDE_PATH=$NAME; # Which input file to use # Possible Scenarios: # 1. input_file was provided by user # 2. input_file was not entered, but current path is within a RUN directory # 3. input_file was not entered & current path is not within any RUN dir my $run_ = "RUN"; my $in_ = "input"; if ( $input ne "" ) { # If $input is not null, copy value if ( `echo $input | grep "\/" $redirect ` ) { # A run directory AND input file name was provided my $last_slash = rindex($input, "\/"); $run_ = substr($input,0,$last_slash); # Copy section of path PRIOR to $input's last slash if ( rindex($run_, "\/") != -1 ) { my $new_slash = rindex($run_, "\/"); $run_=substr($run_,($new_slash+1)); # Copy section of path AFTER $run_'s last slash } $in_=substr($input,($last_slash+1)); # Copy section of path AFTER $input's last slash } else { # A path was not provided, but an input file name was. $in_ = $input; if ( -e $input ) { # Assume that this script is being executed in the desired RUN dir. my $last_slash = rindex($pwd_, "\/"); $run_=substr($pwd_,($last_slash+1)); # Copy section of path AFTER $pwd_'s last slash } else { # The input file does not exist in current working directory. # Search for it in the RUN directories with recent time stamps. # List, then sort (comma delimited) all RUN_names by mod time open $fh, "ls -vtmBd ".$NAME."/".$run_."* ".$redirect." |"; { local $/; $run_ = trim(<$fh>); # remove whitespace } close $fh; # Copy the single RUN_name PRIOR to the first comma (if any) if ( index($run_, ",") != -1 ) { my $first_comma = index($run_, ","); $run_ = substr($run_,0,$first_comma); } # Results show full path; strip off everything from first to last slash my $last_slash = rindex($run_, "\/"); $run_=substr($run_,($last_slash+1)); # copy all chars after the last forward slash } } } else { if ( `echo $pwd_ | grep $run_ $redirect ` ) { my $name_slash = rindex($pwd_, $sim_); # Find string index where simulation's name begins my $next_slash = index($pwd_, "\/", $name_slash); # Find string index where simulation's name ends $run_=substr($pwd_,($next_slash+1)); # Copy section of path AFTER $NAME # Copy portion of results PRIOR to first slash (if any) # This would imply that the path of execution was nested # down past the RUN directory path if ( index($run_, "\/") != -1 ) { my $new_slash = index($run_, "\/"); $run_ = substr($run_,0,$new_slash); } } else { # Script was executed outside of a RUN directory # List, then sort (comma delimited) all RUN_names by mod time open $fh, "ls -vtmBd ".$NAME."/".$run_."* ".$redirect." |"; { local $/; $run_ = trim(<$fh>); # remove whitespace } close $fh; # Copy the single RUN_name PRIOR to the first comma (if any) if ( index($run_, ",") != -1 ) { my $first_comma = index($run_, ","); $run_ = substr($run_,0,$first_comma); } # Results show full path; strip off everything from first to last slash my $last_slash = rindex($run_, "\/"); $run_=substr($run_,($last_slash+1)); # copy all chars after the last forward slash } } my $INPUT=""; if ( $use_input == 0 ) { # Execute S_MAIN_*.exe without an input file (e.g. sie, --version, etc.) } else { $INPUT=$NAME."/".$run_."/".$in_; } if ( $echo_ON == 1 ) { print "debug: setting INPUT equal to \"$INPUT\"\n"; } if ( (! -e $INPUT) && ($use_input!=0) ) { print "\e[31mError:\e[00m Input file... \n\"$INPUT\" \n...is not a valid file name.\n"; if ( $nexiom_format == 1 ) { print " Please overwrite NExIOM value by adding \"-i \"\n"; } printf " \e[34mPlease set to a valid input file name\e[00m\n"; printf " \e[34mfrom one of the following directories:\e[00m\n"; system("ls -vtmBd ".$NAME."/RUN*"); print "Type \"start_sim -help\" for more info.\n"; exit; } my $CMD = $NAME."/".$S_MAIN_EXE." ".$INPUT." ".$options ; if ( ! -e $NAME."/".$S_MAIN_EXE ) { print "\n" ; print "\e[31mError: \"$S_MAIN_EXE\" does not exist.\e[00m\n"; print " \e[33mThe executable that you were expecting may not have \e[00m\n"; print " \e[33mbeen created yet, or maybe was created on a different \e[00m\n"; print " \e[33mplatform and will not run on this one.\n"; print "File(s) Found:\n"; print "\e[34m"; system("ls -apv S_main_*"); print "\e[00m"; print "Try running CP, then start_sim again.\n\n"; exit; } else { if ( $help == 1 ) { # First print help info from S_main_*.exe. Defined in file: # $TRICK_HOME/trick_source/sim_services/exec/process_sim_args.c system($NAME."/".$S_MAIN_EXE." ".$options); # Now append start_sim specific help info. print "$usage\n"; exit; } else { if ( $nexiom_format == 1 ) { $nexiom_output = process_nexiom_input( $nexiom_input ); # strip off everything up to and including the last slash my $last_slash = rindex($nexiom_output, "\/"); $nexiom_output = substr($nexiom_output,($last_slash+1)); if ( $echo_ON == 1 ) { #print "setting NExIOM output file to \"$nexiom_output\"\n"; } #$CMD = $CMD." -auto -nexiom ".$nexiom_output; $CMD = $CMD." -auto -nexiom "; } if ( $DDD == 1 ) { # Prepend command with ddd $CMD = "ddd --args ".$CMD; } elsif ( $GDB == 1 ) { # Prepend command with gdb $CMD = "gdb --args ".$CMD; } } } # START THE SIM!!! if ( $echo_ON == 1 ) { print "starting the simulation:\n"; print " \"$CMD\"\n\n"; } system("cd ".$NAME."; ".$CMD); ####################################################################### ######################## DECLARE SUBROUTINES ######################## ####################################################################### sub process_nexiom_input { # PARSE NExIOM INPUT FILE my ($infile, $tag) ; my ($value, $outfile) ; # If two arguments received (input_filename, XML tag) if ( $_[1] ) { # save input_filename and XML tag strings ($infile, $tag) = handle_nexiom_args($_[0], $_[1]) ; # One argument received (input_filename) } else { # save input_filename; XML tag is null ($infile, $tag) = handle_nexiom_args($_[0], 0) ; } # If an XML tag was provided if ( $tag ) { # Save the value of the first occurrence of this XML tag. $value = get_tag_val($infile, $tag) ; # This value is most commonly used as the NExIOM default SIM directory name. return $value; # No XML tag provided } else { # Translate input file to "nexcsims.d" $outfile=generate_data_files($infile) ; return $outfile; } } sub handle_nexiom_args($$) { my ( $infile, $tag ) ; $infile = $_[0] ; $tag = $_[1] ; # Exit script if input_file does not exist if ( ! -e $infile ) { print "ERROR: Can't find NExIOM input file \"$infile\" \n" ; print "File does not exist: $infile \n" ; exit } return $infile, $tag ; } sub get_tag_val($$) { my ( $file ) = $_[0] ; my ( $tag ) = $_[1] ; my $value ; # Parse XML contents to hashref, $data my $data = XML::Simple::XMLin($file) ; # Find first occurrence of XML tag (i.e. ) if ( ! ($value = $data->{$tag}) ) { # Couldn't find this XML tag, so clear $value $value = "" ; } return $value ; } sub generate_data_files($) { my ( $file ) = $_[0] ; my $results; my $record_name; $record_index = 0; # counter for the number of vars to data record # Save the value of the first occurrence of this XML tag. if ( ! ($results = get_tag_val($file,"results")) ) { # Couldn't find this XML tag, so provide default value $results = "nexiom_output.xml" ; } # strip off everything up to and including the last slash my $last_slash = rindex($results, "\/"); $record_name=substr($results,($last_slash+1)); # copy all chars after the last forward slash # create/overwrite nexcsims.d data file open (FILE, ">".$DATA_PATH."/Modified_data/nexcsims.d"); close FILE; # create/overwrite nexcsims.dr recording file open (DR_FILE, ">".$DATA_PATH."/Modified_data/nexcsims.dr"); # Insert header declarations print DR_FILE "#ifndef DR_GROUP_ID\n"; print DR_FILE " #define DR_GROUP_ID sys.exec.record.num_group\n"; print DR_FILE "#else\n"; print DR_FILE " DR_GROUP_ID -- ;\n"; print DR_FILE "#endif\n\n"; print DR_FILE "sys.exec.record.group[DR_GROUP_ID].name = \"$record_name\" ;\n"; if ( $freq > 0.0 ) { print DR_FILE "sys.exec.record.group[DR_GROUP_ID].cycle = $freq ;\n"; } print DR_FILE "sys.exec.record.group[DR_GROUP_ID].record = Yes ;\n"; print DR_FILE "sys.exec.record.group[DR_GROUP_ID].format = DR_Binary ;\n"; close DR_FILE; # Create a new XML::Parser object, $parser my $parser = XML::Parser->new( Handlers => { # 'Start' handler - references the start subroutine (hand-written below) Start=>\&start, # 'End' handler - reference the end subroutine (hand-written below) End=>\&end, } ); $parser->parsefile($file); # Append footer to recording file open (DR_FILE, ">>".$DATA_PATH."/Modified_data/nexcsims.dr"); print DR_FILE "\nDR_GROUP_ID ++ ;\n"; close DR_FILE; return $results ; } sub start { # Called by generate_data_files() my ( $expat, $element, %attrval ) = @_; my ( $i, $j, $k ); my $dimension_counter = 0; my @array_of_arrays = (); my $pos = 1; # Search for tag to build an input data file if ( $element eq "Parameter" and $element_stack[ -1 ] eq "InputParameters" ) { # Append variables to data file open (FILE, ">>".$DATA_PATH."/Modified_data/nexcsims.d"); # Translate the NExIOM variable name to a local variable name my ($in_variable, @arrays) = find_sim_var( $attrval{name}, "in" ) ; # Determine if var is scalar, single dimension array, double (matrix), etc # If @arrays list is empty, then this variable is scalar $dimension_counter = @arrays; for ( $i = 0; $i < $dimension_counter; $i++ ) { push @array_of_arrays, [1..$arrays[$i]]; } $pos = 1; # default array size my $iter = make_permutation(@array_of_arrays); while (my @elements = $iter->() ){ print FILE "$in_variable"; if ($dimension_counter >= 1) { foreach (@elements) { $pos = $_ - 1; # decrease array position by 1 for C code print FILE "[$pos]"; # append array index to variable } } if( exists $attrval{units} ) { # Identify what units this parameter's value will be given in print FILE " \{$attrval{units}\}"; } # Retrieve the value being assigned to this parameter print FILE " = $attrval{value};\n"; } close FILE; # Search for tag to build a data recording file } elsif ( $element eq "Parameter" and $element_stack[ -1 ] eq "OutputParameters" ) { # Append variables to recording file open (DR_FILE, ">>".$DATA_PATH."/Modified_data/nexcsims.dr"); # Translate the NExIOM variable name to a local variable name my ($out_variable, @arrays) = find_sim_var( $attrval{name}, "out" ) ; # Determine if var is scalar, single dimension array, double (matrix), etc # If @arrays list is empty, then this variable is scalar $dimension_counter = @arrays; for ( $i = 0; $i < $dimension_counter; $i++ ) { # Create an array of integers from 1 to n, # then populate that array into an array list. # Example: given a multi-dimensional array that is of size 2 x 10 x ?, # produce this array list => ([1..2], [1..10], [1..?], etc... ) push @array_of_arrays, [1..$arrays[$i]]; } $pos = 1; # default array size my $iter = make_permutation(@array_of_arrays); while (my @elements = $iter->() ){ print DR_FILE "sys.exec.record.group[DR_GROUP_ID].ref". "[$record_index] = \"$out_variable"; if ($dimension_counter >= 1) { foreach (@elements) { $pos = $_ - 1; # decrease array position by 1 for C code print DR_FILE "[$pos]"; # append array index to variable } } # End string with closed quote, semi-colon & newline print DR_FILE "\" ;\n"; # write to data recording file $record_index++; } close DR_FILE; } push @element_stack, $element; } sub end { my ( $expat, $element ) = @_; pop @element_stack; } sub find_sim_var { # Use a mappping file to find which simulation variable # is equivalent to this nexiom input variable name. my $nexiom_variable = my $new_variable = shift; my $in_out = shift; my ($from, $to, @slots); if ((defined $in_out) && ($in_out eq "out")) { open (MAP_FILE, $INCLUDE_PATH."/nexiom_output.map"); } else { open (MAP_FILE, $INCLUDE_PATH."/nexiom_input.map"); } while () { chomp; # read file until another newline character is found # Copy values (assume data is comma delimited) ($from, $to, @slots) = split(","); $from = trim($from); # remove whitespace $to = trim($to); # remove whitespace # Values in the output mapping file are opposite/backwards if ((defined $in_out) && ($in_out eq "out")) { # Swap from & to values ($from, $to) = ($to, $from); } foreach (@slots) { $_ = int(trim($_)); # save array demensions (if any) } # Search for the nexiom variable name if( $nexiom_variable eq $from ) { # Cross mapping found. Rename to local simulation name. $new_variable = $to ; last; # break out of loop } elsif( $nexiom_variable eq $to ) { # No cross mapping needed. Leave variable name as is. $new_variable = $nexiom_variable ; last; # break out of loop } else { # clear tmp values prior to parsing next line of MAP_FILE ($from, $to, $#slots) = ( "", "", -1); } } close (MAP_FILE); my @found_it = ($new_variable, @slots) ; return @found_it ; } sub make_permutation{ my @refs = @_; my @arrayindexes = (); foreach (@refs){ push @arrayindexes,[$_,0,$#{$_}]; } return sub { return if $arrayindexes[0]->[1] > $arrayindexes[0]->[2]; my @elements = map { $_->[0]->[ $_->[1]] } @arrayindexes; # Check for out of bounds.... $arrayindexes[$#arrayindexes]->[1]++; for (my $i = $#arrayindexes; $i > 0; $i--) { if ($arrayindexes[$i]->[1] > $arrayindexes[$i]->[2]) { $arrayindexes[$i]->[1] = 0; $arrayindexes[$i-1]->[1]++; } else { last; } } return @elements; }; } sub trim($) { # Remove whitespaces my $string = $_[0]; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; }