object(NonWhitespace)#3051 (2) {
["contents"]=>
string(21) "\"(\$[[:alnum:]_]+)\""
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3041 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3275 (2) {
["contents"]=>
string(12) "[^[:blank:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3263 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3575 (2) {
["contents"]=>
string(13) "^[[:blank:]]#"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3561 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3759 (2) {
["contents"]=>
string(12) "[[:alnum:]_]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#3605 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#4589 (2) {
["contents"]=>
string(11) "[[:blank:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#4577 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#5193 (2) {
["contents"]=>
string(15) "([[:space:]]|[\"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#5281 (2) {
["contents"]=>
string(19) "([^[:alnum:]_\"\$])"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#5271 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#5869 (2) {
["contents"]=>
string(11) "[[:space:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#6371 (2) {
["contents"]=>
string(11) "[[:space:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#6359 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#6861 (2) {
["contents"]=>
string(6) "(.*)\)"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#6835 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7221 (2) {
["contents"]=>
string(26) "^[[:blank:]]+[^[:blank:]]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7209 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7331 (2) {
["contents"]=>
string(4) "^#.*"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7319 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7373 (2) {
["contents"]=>
string(20) "^declare[[:blank:]]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7361 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7413 (2) {
["contents"]=>
string(18) "^local[[:blank:]]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7401 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7609 (2) {
["contents"]=>
string(21) "^function[[:blank:]]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7599 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7649 (2) {
["contents"]=>
string(8) "^[\{\}]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7637 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7683 (2) {
["contents"]=>
string(19) "^return[[:blank:]]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7673 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7785 (2) {
["contents"]=>
string(10) "^[^\(]*\)$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7775 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#7851 (2) {
["contents"]=>
string(38) "^[[:alnum:]_\.\/-]+[^[:alnum:]_\.\/-]$"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#8705 (2) {
["contents"]=>
string(19) "\"?\$([[:digit:]]*)"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#8687 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#8805 (2) {
["contents"]=>
string(19) "\"?\$([[:digit:]]*)"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#8793 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9081 (2) {
["contents"]=>
string(17) "^(\[)+[[:space:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9069 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9109 (2) {
["contents"]=>
string(20) "^(\[|\])+[[:space:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9097 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9137 (2) {
["contents"]=>
string(12) "^[[:space:]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9125 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9173 (2) {
["contents"]=>
string(42) "^(.*)[[:blank:]]+(-[[:alpha:]]+|==|\!=|=~|"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9605 (2) {
["contents"]=>
string(46) "(.*)[[:blank:]]+=~[[:blank:]]+(.*)[[:blank:]]*"
["children":protected]=>
array(0) {
}
}
object(NonWhitespace)#9591 (2) {
["contents"]=>
string(2) "]]"
["children":protected]=>
array(0) {
}
}
#! /bin/bash ##! Author: Pieter van der Star (info@pietervanderstar.nl) ##! Modifications by: (unmodified) ##! ##! @warning Do not use this script if your input is large and you are impatient. ##! @copyright This program is free software: you can redistribute it and/or modify ##! it under the terms of the GNU Affero General Public License as ##! published by the Free Software Foundation, either version 3 of the ##! License, or (at your option) any later version. ##! \n ##! This program is distributed in the hope that it will be useful, ##! but WITHOUT ANY WARRANTY; without even the implied warranty of ##! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##! GNU Affero General Public License for more details. ##! \n ##! You should have received a copy of the GNU Affero General Public License ##! along with this program. If not, see <https://www.gnu.org/licenses/>.
#Changelog: #┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━┓ #┃ 2 nov 2023 │ First header information in place │ Pieter van der Star ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 9 nov 2023 │ First release │ Pieter van der Star ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 10 nov 2023 │ - Added support for function call │ Pieter van der Star ┃ #┃ │ detection when called in conditional. │ ┃ #┃ │ - Added function call with piping using │ ┃ #┃ │ '<' where the pipe operator and operand│ ┃ #┃ │ are not recognised as arguments. │ ┃ #┃ │ - Documented the last undocumented │ ┃ #┃ │ functions. │ ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 22 dec 2023 │ - Added more function documentation │ Pieter van der Star ┃ #┃ │ - Removed unused function │ ┃ #┃ │ consume_comment. │ ┃ #┃ │ - Extended operator support in │ ┃ #┃ │ parse_assignment. │ ┃ #┃ │ - Minor style improvements. │ ┃ #┃ │ - Fixed bug where assignments were │ ┃ #┃ │ sometimes rewritten as 0 or 1 │ ┃ #┃ │ (depending on the const status). │ ┃ #┃ │ - Added argument flags and argument │ ┃ #┃ │ checking. │ ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 20 feb 2024 │ - Added support for "name() {" fuctions. │ Pieter van der Star ┃ #┃ │ - Improved documentation. │ ┃ #┃ │ - Removed debug code │ ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 22 feb 2024 │ - Added translation for $# into │ Pieter van der Star ┃ #┃ │ func_num_args(). │ ┃ #┃ │ - Fixed bug where $ was appended to ints │ ┃ #┃ │ in arithmatic in assignement. │ ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 24 feb 2024 │ - Fixed a lot of small bugs after testing│ Pieter van der Star ┃ #┃ │ on larger code base (my own scripts). │ ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 25 feb 2024 │ - Revized conditional handling. │ Pieter van der Star ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 29 feb 2024 │ - Added support for heredoc in │ Pieter van der Star ┃ #┃ │ conditional handling. │ ┃ #┃ │ - Added trailing newline to │ ┃ #┃ │ update_function error message. │ ┃ #┃ │ - Added for loop support. │ ┃ #┃ │ - Added support for indirect expansion in│ ┃ #┃ │ variables. │ ┃ #┃ │ - Added support for hyphen ('-') in │ ┃ #┃ │ command name. │ ┃ #┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
##! @file ##! @brief This script transforms bash scripts into php-ish that Doxygen can parse. ##! ##! @todo Review function documentation. ##! @todo Add support for [[ regex without if. ##! @todo Add support for "source <script>". ##! @todo Add support for line continuation ("\" followed by newline). ##! @todo Add return type other than int if the function doesn't contain a return statement. (Also handle return void.) ##! @todo Count the newlines in multiline condition (when using heredocs) for linenumber in error messages. ##! @bug The line number for variable errors (such as incorrect flag to declare) seems to be one line too early. ##! @bug When the source document is updated while the filter is running the filter sometimes ends ##! up in an endless loop showing the error "Unknown item in get_composition_element_size". ##! ##! This script works by parsing the Bash script file and then outputting matching PHP code on ##! stdout. The parsing stores each tokens in one big flat array. This array is called ##! composition. To identify each kind of token the first element is an identifier, which ##! determines how many elements follow. Thy are stored as listed in the table below: ##! |Purpose |Identifier|Elements[1]| Purpose of element | ##! |:-----------------------|:---------|----------:|:----------------------------------------| ##! |shebang |! | 1| Contents of the shebang | ##! |Doxygen comment |d | 1| Contents of the comment | ##! |comment |# | 1| Contents fo the comment | ##! |newline |n | -| | ##! |variable |v | 1| Indicates readonly variable | ##! | | | 2| Type of the variable | ##! | | | 3| Name of the variable | ##! | | | 4| Assigned value, or empty if not assigned| ##! |function |f | 1| Name of the function | ##! | | | 2| Number of parameters, can be 0 if none | ##! | | | >2 (odd)| Name of the parameter | ##! | | | >2 (even)| Type of the parameter | ##! |scope opening |{ | -| | ##! |scope closing |} | -| | ##! |command/function call |c | 1| The command | ##! | | | 2| Number of arguments, can be 0 if none | ##! | | | >2| Parameter values | ##! |flow control |j | 1| Type of control (if, while else etc) | ##! | | | 2| The condition | ##! |return statement |r | -| | ##! |variable assignment |= | 1| The operand | ##! | | | 2| The operator | ##! | | | 3| The value | ##! |whitespace |w | 1| The value | ##! |switch option |o | 1| The value | ##! |break statement (;;) | | -| | ##! @b 1 Elements is the index of the element after the identifier. ##! @note The matching code may not be valid php but is good enough for Doxygen to parse. ##! @note Not all traits of the bash language map to php. This script tries to map as close as possible, and otherwise discards this information.
##! ##! The script works in three stages: ##! -# Parsing ##! -# Analysis ##! -# Writing ##! \n ##! Parsing extracts the meaningful data from the script file.\n ##! Analysis enhances that data.\n ##! Writing outputs the data. ##! ##! This code is written with readibility as first priority (after the "it should do its job" ##! requirement). ##! ##! # How to use: ##! -# Add this file to the projects root directory (where your doxyfile lives). ##! -# Update some of the doxyfile values. ##! -# First tell Doxygen to document .sh files as if it is a php file: ##! <tt>EXTENSION_MAPPING = sh=php</tt>. ##! -# Then Doxygen needs to know about this filter: ##! <tt>FILTER_PATTERNS = *.sh=./doxygen-bash-filter.sh</tt>. ##! -# add .sh to the FILE_PATTERNS list. ##! -# Optionally add doxygen-bash-filter.sh to the EXCLUDE values. ##! <tt>EXCLUDE = doxygen-bash-filter.sh</tt>. ##! @note When @c EXTENSION_MAPPING, @c FILTER_PATTERNS or @c EXCLUDE is already set add the values to the ##! the list.
##! @defgroup return_values Return values ##! @brief List of return values used in this script. ##! @{
##! Return value for success. declare -i -r C_SUCCESS=0; ##! Return value on parse error. declare -i -r C_PARSE_ERROR=1; ##! Return value on error when analysing parsed composition. declare -i -r C_ANALYSIS_ERROR=2; ##! Return value on error when writing result. declare -i -r C_WRITE_ERROR=3;
##! @brief This is a function just to test the function declaration option not used in the rest of this document.
test() { echo"This is a function and here just as a test";
} ##! @brief This is a function just to test the function declaration option not used in the rest of this document.
test2 () { echo"This is a function and here just as a test";
}
##! @brief This is a function just to test the function declaration option not used in the rest of this document. function test3() { echo"This is a function and here just as a test";
}
##! @brief Reads the next character from the input stream ##! @post Sets inherited $char with the next character, unless it is an escaped char in which case it. ##! @post Sets the char variable to two chars ( '\' and \<char\>). function get_next_char { read -N 1 -r char; if [ "$char"=="\\" ]; then read -N 1 -r second_char;
char="$char$second_char" fi
} ##! @brief Removes the leading and trailing double quotes " from the given string. ##! @param $arg1 Name of the string variable to remove the quotes from ##! @post The string named by $arg1 has its leading and trailing double quotes removed. function strip_quotes_if_variable { local -n string="$1"; if [[ "$string"=~ ; then string=${BASH_REMATCH[1]}; fi
}
##! @brief Discard whitespaces (except newline) from the input stream. ##! @param $arg1 Should the characters be appended to token? (1 yes, !=1 no) ##! @returns Error code according to @ref return_values. ##! @post Sets the inherited $char to the first non-whitespace character gotten via ##! @ref get_next_char(). ##! @post Updates inherited $token (depending on $arg1) with all characters until a closing quote is ##! encountered. ##! @note For the purposes of this function "\n" is not a whitespace character. function consume_whitespaces { local -i append_token="$1"; if [ "$append_token"-eq 1 ]; then
token+="$char"; fi while [ "$char" != "" ]; do if [[ "$char"=~ ; then return $C_SUCCESS; fi if [ "$append_token"-eq 1 ]; then
token+="$char"; fi
get_next_char; done return $C_SUCCESS;
}
##! @brief Append everything to $token untill a command separator is encountered. ##! @returns Error code according to @ref return_values. ##! @post Updates inherited $token with all characters until a command separator is encountered. ##! @post Sets the inherited $char to the separator character (except when "<space>#", then sets ##! to "#". ##! @note Separators are: ##! - "\n" ##! - ";" ##! - "<non-newline whitespace>#" function consume_until_cmd_separator { while [ "$char" != "" ]; do if [ "$char"=="\"" ]; then #unescaped '"' consume_until_closing_quote 1; continue; fi if [[ "$char" == "\'" ]]; then#unescaped '''
consume_until_closing_quote 1; continue; fi if [ "$char"== $'\n' ]; then#newline return $C_SUCCESS; fi if [ "$char"== $';' ]; then#unescaped ';' return $C_SUCCESS; fi if [[ "${token: -1}$char"=~ ; then# whitespace followed by '#' return $C_SUCCESS; fi
token+="$char";
get_next_char; done return $C_SUCCESS;
}
##! @brief Append everything to $token untill a non word character is encountered. ##! @returns Error code according to @ref return_values. ##! @post Updates inherited $token with all characters until a non word character is encountered. ##! @post Sets the inherited $char to the non word character ##! @note Word characters are: ##! - Letters A to Z both upper and lowercase ##! - The underscore character ("_") function consume_until_non_word_char { while [[ "$char"=~ ; do
token+="$char";
get_next_char; done return $C_SUCCESS;
} ##! @brief Append everything to $token untill a closing quote is encountered. ##! @param $arg1 Should the characters be appended to $token? (1 yes, !=1 no) ##! @returns Error code according to @ref return_values. ##! @post Updates inherited $token (depending on $arg1) with all characters until a closing quote is ##! encountered. ##! @post Sets the inherited $char to the closing quote. function consume_until_closing_quote { local -i append_token="$1";
local -r opening="$char"; if [ "$append_token"-eq 1 ]; then
token+="$char"; fi while [ "$char" != "" ]; do
get_next_char; if [ "$append_token"-eq 1 ]; then
token+="$char"; fi if [[ "$char"=="$opening" ]]; then
get_next_char; return $C_SUCCESS; fi done echo"Warning: missing closing ${opening}-character @line: $linenumber." 1>&2; return $C_SUCCESS;
}
##! @brief Append everything to $token until the matching closing bracket (']') is encountered. ##! @returns Error code according to @ref return_values. ##! @post Updates inherited $token with all characters until the matching closing bracket is ##! encountered. ##! @post Sets the inherited $char to the closing bracket. function consume_until_closing_square_bracket {
token+="$char"; local -i level=1;
get_next_char; while [[ "$char" != "" && $level -gt 0 ]]; do if [ "$char"=="\"" ]; then #unescaped '"' consume_until_closing_quote 1; continue; fi if [[ "$char" == "\'" ]]; then#unescaped '''
consume_until_closing_quote 1; continue; fi
token+="$char";
if [ "$char"=="[" ]; then level=$((level+1)); fi if [ "$char"=="]" ]; then level=$((level-1)); fi
get_next_char; done return $C_SUCCESS;
}
##! @brief Consumes all characters until a matching closing parenthesis is found. ##! @returns Error code according to @ref return_values. ##! @post Updates inherited $token with all characters until the matching closing bracket is encountered. ##! @post Sets the inherited $char to the closing parenthes. function consume_until_closing_parenthesis {
token+="$char"; local -i level=1;
get_next_char; while [[ "$char" != "" && $level -gt 0 ]]; do if [ "$char"=="\"" ]; then #unescaped '"' consume_until_closing_quote 1; fi if [ "$char" == "\'" ]; then#unescaped '''
consume_until_closing_quote 1; fi
token+="$char"; if [ "$char"=="(" ]; then level=$((level+1)); fi if [ "$char"==")" ]; then level=$((level-1)); fi
get_next_char; done return $C_SUCCESS;
}
##! @brief Append everything to $token untill a whitespace is encountered. ##! @returns Error code according to @ref return_values. ##! @post Updates inherited $token with all characters until a whitespace is encountered. ##! @post Sets the inherited $char to the first whitespace. function consume_until_space {
get_next_char; while [ "$char" != "" ]; do if [[ "$char"=~ ; then return $C_SUCCESS; fi
token+="$char";
get_next_char; done return $C_SUCCESS;
}
##! @brief Parse the comment and add it to the inherited composition array. ##! @returns Error code according to @ref return_values. ##! @post Upon successful paring of the comment the inherited $token is cleared. ##! @post Sets the inherited $char to the first character after the comment. function parse_comment { localindex=${#composition[@]};
get_next_char; while [[ "$char" != "" && "$char" != $'\n' ]]; do
token+="$char";
get_next_char; done
if [ "${token:1:2}"=="! " ]; then#check if comment is shebang
composition[$index]="!"; #indicate we have shebang
token="${token:2}"; elif [ "${token:1:2}"=="#!" ]; then#check if comment is a doxygen comment
composition[$index]="d"; #update to a doxygen comment
token="${token:3}"; else
composition[$index]="#"; #indicate we have a "normal" comment
token="${token:1}"; fi
composition[$((index+1))]="$token";
token=""; if [ "$char" != $'\n' ]; then echo"ERROR"; return 100 fi return $C_SUCCESS;
}
##! @brief Parse a variable and add it to the inherited composition array. ##! @returns Error code according to @ref return_values. ##! @pre Expects the inherited $char to be set to the first character of the function to handle. ##! @post Clears the inherited $token upon completion. Sets the inherited char to the first char after the variable function parse_variable { localindex=${#composition[@]};
composition[$index]="v"; #this is a variable composition[$((index+1))]=0; #1 if readonly, otherwise 0
composition[$((index+2))]="str"; #type
composition[$((index+3))]=""; #name
composition[$((index+4))]=""; #assignment (if any)
local -i -r S_PROPERTY=1; local -i -r S_NAME=2; localstate=0; #using parents char; while [ "$char" != "" ]; do
case "$state" in "$S_PROPERTY") {
case "$char" in "r") composition[$((index+1))]=1;; "i") composition[$((index+2))]="int";; "a") composition[$((index+2))]="list";; "A") composition[$((index+2))]="list";; "g") ;; ##! @todo add support for global "n") ;; ##! @todo how to handle this? Seems only interesting with regards to arguments which then get a reference instead of value. " ") state=0;;
*) return $C_PARSE_ERROR;
esac
};; "$S_NAME") { ##! @todo Change name handling so a name of the shape "$name" (including the quotes is allowed. Quick fix now allows both " and $ everywhere in the name. if [[ "$char"=~ ;=]) ]]; then break; elif [[ "$char"=~ ; then return $C_PARSE_ERROR; fi
composition[$((index+3))]+="$char";
};;
*) {
consume_whitespaces 0; if [ "$char"=="-" ]; then
state="$S_PROPERTY"; else
state="$S_NAME"; continue; #skip reading new char, process first fi
};;
esac
get_next_char; done;
if [ "$char"=="=" ]; then
token="";
get_next_char;
consume_until_cmd_separator;
composition[$((index+4))]="$token";
token=""; return $C_SUCCESS; fi
token="";
get_next_char; return $C_SUCCESS;
}
##! @brief Parse a function declaration and add it to the inherited composition array. ##! @post Updates char to the last char of the function. ## Start by getting the name, then just consume until the opening bracket, discarding the stuff beteen. function parse_function { localindex=${#composition[@]};
composition[$index]="f"; #this is a function
composition[$((index+1))]=""; #name composition[$((index+2))]=0; #placeholder for number of parameters
#before we get the name we need to discard the whitespaces in between the function keyword and the name
consume_whitespaces;
token="";
#get the name
consume_until_non_word_char;
composition[$((index+1))]="$token";
#then just consume until the opening bracket while [ "$char" != "{" ]; do
get_next_char; done;
get_next_char;
token=""; return $C_SUCCESS;
}
##! @brief Parse a function or command call and add it to the inherited composition array. ##! @returns Error code according to @ref return_values. ##! @pre Expects inherited $token to be set to the function name. ##! @pre Expects inherited $char to be set to the first char after the function name. function parse_command { localindex=${#composition[@]};
composition[$index]="c"; #this is a command/call
composition[$((index+1))]="$token"; composition[$((index+2))]=0; #number of arguments
local -i nargs; nargs=0;
consume_whitespaces 0;
token=""; #get the parameters while [ "$char" != "" ]; do if [[ "$char"=="<" ]]; then#input piping operator shouldn't be considered an argument
consume_until_space; #The thing being piped however can be considered an argument, so ##! @todo conditionally add the thing being piped as argument based on command line parameter.
consume_until_cmd_separator;
token=""; return; elif [[ "$char"=~ || "$char"==";" ]]; then if [ ${#token} -gt 0 ]; then if [[ "$nargs"-eq 0 && "$token"=="()" ]]; then #This isn´t a command but a function definition
composition[$index]="f"; #update to a function #index+1 can stay as is, the content is correct, name is the same token #index+1 can stay as is, the content is correct for now 0 parameters
token="";
consume_whitespaces; #then just consume until the opening bracket while [ "$char" != "{" ]; do
get_next_char; done;
get_next_char;
token=""; return $C_SUCCESS; fi nargs=$((nargs+1));
composition[$((index+2+nargs))]="$token";
token=""; fi if [[ "$char"== $'\n' || "$char"==";" ]]; then break; fi elif [[ "$char"=="\"" || "$char" == "\'" ]]; then
consume_until_closing_quote 1; continue; else
token+="$char"; fi
get_next_char; done; if [[ "$char" != $'\n' ]]; then
get_next_char; fi
composition[$((index+2))]="$nargs"; #number of arguments return $C_SUCCESS;
}
##! @brief consumes input until the keyword ($arg1) has been found. ##! @param $arg1 Keyword to search for. ##! @returns Error code according to @ref return_values. ##! @retval $C_SUCCESS if keyword found. ##! @retval $C_PARSE_ERROR if keyword not found before end of file occurred. ##! @post Sets the inherited $token to everything before the keyword. ##! Sets the inherited $char to the first whitespace following the keyword. ##! @todo handle parenthesis, if keyword is within the parenthesis it doesn't count. function consume_until_keyword { local word=""; #shellcheck disable=2078 #While true by design. while [ : ]; do
get_next_char if [ "$char"=="" ]; then return $C_PARSE_ERROR; fi if [[ "$char"=~ ; then if [ "$word"=="$1" ]; then return $C_SUCCESS; fi
token+="$word$char";
word=""; continue; fi
word+="$char"; done
}
##! @brief Parse a flow control statement and add it to the inherited composition array. ##! @returns Error code according to @ref return_values. ##! @pre Expects the inherited $token to contain the control flow keyword. ##! @post Clears the inherited $token upon successful return. function parse_flow_control { local keyword="$token"; #control keyword localindex=${#composition[@]};
composition[$index]="j"; #this is flow control (j for jumps)
composition[$((index+1))]="$keyword"; #control keyword
composition[$((index+2))]=""; #condition local end_keyword="";
##! @brief Gets the condition of the switch. ##! @returns Error code (for now always success) according to @ref return_values. ##! @post Updates the condition field in the composition element. function parse_case { #get the switch condition
token=""; while [[ !"$token" =~ [[:blank:]]?(.*)[[:blank:]]+in ]]; do
consume_until_cmd_separator; done
token="${BASH_REMATCH[1]}"; #remove the " in" part
strip_quotes_if_variable "token";
composition[$((index+2))]="$token"; #condition
token=""; return $C_SUCCESS;
}
##! @brief Gets the value of the switch case. ##! @returns Error code according to @ref return_values. ##! @post Adds the case option to the composition. function parse_case_option { localindex=${#composition[@]};
composition[$index]="o"; #this is a switch option (o for option) if [[ "$token"=~ ; then
composition[$((index+1))]="${BASH_REMATCH[1]}"; #option value else return $C_PARSE_ERROR; fi return $C_SUCCESS;
}
##! @brief Extract variable name and value from assignment statements. ##! @returns Error code (for now always success) according to @ref return_values. ##! @pre Expects the inhertied $token to be filled with the value being assigned. ##! @post Clears the inherited $token. ##! ##! Adds the assignment information to the composition. function parse_assignment { localindex=${#composition[@]};
composition[$index]="="; #this is an assignment
token+="$char";
case "${token: -2}" in "+="|"-="|"*="|"/="|"%="|"<<="|">>="|"&="|"^="|"|=") {
composition[$((index+1))]="${token::-2}"; #operand
composition[$((index+2))]="${token: -2}"; #operator
};;
*){
composition[$((index+1))]="${token::-1}"; #operand
composition[$((index+2))]="${token: -1}"; #operator
};;
esac
token="";
get_next_char;
consume_until_cmd_separator;
composition[$((index+3))]="$token"; #value
token="";
return $C_SUCCESS;
}
##! @brief Parses an sh file into a composition. ##! @returns Error code (for now always success) according to @ref return_values. ##! #!! Reads tokens from the input pipe and parses this into a composition containing the flow of ##! the input script. ##! Call like <tt>parse < "inputfile.sh"</tt>. function parse { IFS=''; local token=""; local char; local -i linenumber=0;
get_next_char; #shellcheck disable=2078 #While true by design. while [ : ]; do if [[ "$token$char"=~ ; then
composition[${#composition[@]}]="w"; #indicate we have whitespace
composition[${#composition[@]}]="$token"; #add the contents
token=""; fi
#if [ "$char" == "(" ]; then # consume_until_closing_parenthesis; # continue; #fi if [ "$char"=="[" ]; then
consume_until_closing_square_bracket; continue; fi if [[ "$char"=="\"" || "$char" == "\'" ]]; then
consume_until_closing_quote 1; continue; fi
if [[ "$token$char"=~ ; then
token+="$char"; if! parse_comment; then echo"Comment error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi elif [[ "$token$char"=~ ; then if! parse_variable; then echo"Variable error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi elif [[ "$token$char"=~ ; then if! parse_variable; then echo"Variable error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi elif [[ "$token"== $'\n' ]]; then
composition[${#composition[@]}]="n"; #indicate we have a newline #increment the linecount linenumber=$((linenumber+1)) ##! @todo make this a switch for use during debugging? #echo "Parsing now at line: $linenumber" 1>&2;
token=""; elif [ "$token"== $';' ]; then #check the next char to detect a break ";;". if [ "$char"== $';' ]; then
composition[${#composition[@]}]=";"; #indicate we have a break statement
token="";
get_next_char; fi #just skip over the command separator (if just ;)
token=""; elif [[ "$token$char"=~ ; then if! parse_function; then echo"Function error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi elif [[ "$token"=~ ; then
composition[${#composition[@]}]="$token"; #indicate we have an opening of a scope
token=""; elif [[ "$token"=~ ; then
composition[${#composition[@]}]="r"; #indicate we have a return statement
consume_until_cmd_separator; ##! @todo why do we have an extra space between return and value?
composition[${#composition[@]}]="$token";
token=""; elif [[ "$char"=="=" ]]; then if! parse_assignment; then echo"Assignment error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi continue; elif [[ "$token"=~ ; then
parse_case_option;
token=""; #elif [[ "$token$char" =~ ([[:alnum:]_]*)[[:space:]]*\([[:space:]]*\) ]]; then #echo "Func? \"$token\""; #if ! parse_function "${BASH_REMATCH[1]}"; then # echo "Function error" 1>&2; # return $C_PARSE_ERROR; #fi elif [[ "$token$char"=~ || "$char"=="" || $char ==";" ]]; then
case "$token" in "while"|"for"|"done"|"if"|"elif"|"else"|"fi"|"case"|"esac"|"continue"|"break") { if! parse_flow_control; then echo"flow control error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi continue;
};;
esac
if! parse_command; then echo"Command error @line: $linenumber" 1>&2; return $C_PARSE_ERROR; fi fi
token+="$char"; if [ "$char"=="" ]; then break; fi
get_next_char; done;
if [ ${#token} -ne 0 ]; then echo"token not empty. Still containts: \"$token\"" 1>&2; i=0; printf"ASCII values: " 1>&2; while [ "$i"-lt"${#token}" ]; do printf" %d""'${token:$i:1}" 1>&2; i=$((i+1)); done printf"\n" 1>&2; return $C_PARSE_ERROR; fi return $C_SUCCESS;
}
##! @brief Calculate the offset of the next element in components based on the current one. ##! @param $arg1 Index of the composition element to get the offset for. ##! @returns The offset for this element. function get_composition_element_size { local -i i;
i="$1"; local identifier="${composition[$i]}";
case "$identifier" in "n"|"{"|"}"|";") { #skip next 0 elements (no skip) return 0;
};; "!"|"d"|"#"|"r"|"w"|"o") { # skip next 1 elements return 1;
};; "j"|"f") { return 2;
};; "=") { return 3;
};; "v") { return 4;
};; "c") { #Skip dynamic for command/function call local -i nargs="${composition[$((i+2))]}";#number of arguments return"$((nargs+2))";
};;
*) { echo"Unknown item in get_composition_element_size |$identifier|" 1>&2; #no good value to return, but as the result is often used for increment return 1 so we #get forward return 1;
};;
esac
}
##! @brief Extract function arguments ($n) and updates the function accordingly. ##! @param $arg1 Index of the fuction in the composition. ##! @returns Error code according to @ref return_values (for now always success). ##! ##! Updates the function in the composition. function update_function { local -i composition_index;
composition_index="$1";
local -i i="$composition_index"; localnum_args=0; local -i level=1;
#Search through the functions scope for any variable set to a parameter. #shellcheck disable=SC2078 #Intentional always true condition. Using breaks to terminate loop while [ : ]; do if [ "$i"-gt"${#composition[@]}" ]; then printf"ERROR: Encountered end of file before scope has been closed. \n\tNested level: %d.\n""$level" 1>&2; return $C_ANALYSIS_ERROR; fi
get_composition_element_size "$i"; offset=$?; i=$((i+offset+1));
if [ "${composition[$i]}"=="{" ]; then#handle deepen scope level=$((level+1)); continue; fi if [ "${composition[$i]}"=="}" ]; then#handle "undeepen" scope level=$((level-1)); if [ "$level"-eq 0 ]; then break; fi continue; fi
if [ "${composition[$i]}"=="c" ]; then# "decode" variable declaration local -i num_parameters="${composition[$((i+2))]}"; local -i j=0; while [ "$j"-lt"$num_parameters" ]; do
argument="${composition[$((i+3+j))]}"; #argument to called function/program if [[ "$argument"=~ ; then local -i argument_index="${BASH_REMATCH[1]}"; if [ "$num_args"-lt"$argument_index" ]; then
num_args="$argument_index"; fi fi j=$((j+1)); done fi if [ "${composition[$i]}"=="v" ]; then# "decode" variable declaration local variable="${composition[$((i+4))]}"; fi if [ "${composition[$i]}"=="=" ]; then#"decode" variable assignment local variable="${composition[$((i+3))]}"; fi if [[ "$variable"=~ ; then local -i argument_index="${BASH_REMATCH[1]}"; if [ "$num_args"-lt"$argument_index" ]; then
num_args="$argument_index"; fi fi done;
composition[$((composition_index+2))]="$num_args"; return $C_SUCCESS;
}
##! @brief Analyses the composition and updates information if required. ##! @returns Error code (for now always success) according to @ref return_values. ##! @todo Add error checking where applicable. function analyse { local -i i=0; while [ "$i"-lt ${#composition[@]} ]; do if [ "${composition[$i]}"=="f" ]; then
update_function "$i"; fi
##! @brief Helper for sanitize_condition, parses the condition into variables and operators function parse_condition { local token=""; local char;
#shellcheck disable=2078 #While true by design. while [ : ]; do
get_next_char #echo "char >${char}<" 1>&2; if [ "$char"=="" ]; then break; fi if [[ "$token$char"=~ ; then #ignore the opening brackets
token=""; continue; fi if [[ "$token$char"=~ ; then #ignore the closing brackets
token=""; continue; fi if [[ "$token$char"=~ ; then
consume_whitespaces;
token=""; fi if [[ "$char"=="\"" || "$char" == "\'" ]]; then
consume_until_closing_quote 1; fi if [[ "$token$char"=~ &&|\|\||\!|\]+;)[[:space:]$] ]]; then local condition="${BASH_REMATCH[1]}"; local operator="${BASH_REMATCH[2]}";
if [ "${#condition}"-gt 0 ]; then
conditions[${#conditions[@]}]="v"; #indicate variable
conditions[${#conditions[@]}]="$condition"; fi if [[ "$operator" != "];" && "$operator" != "]];" ]]; then#ignore the closing brackets
conditions[${#conditions[@]}]="o"; #indicate operator
conditions[${#conditions[@]}]="$operator"; fi
token=""; if [ "$char"=="]" ]; then
token="]"; fi continue; fi
token+="$char"; done
#As heredoc always ends with a newline we need to remove that. token=${token::-1} if [ ${#token} -gt 0 ]; then
conditions[${#conditions[@]}]="v"; #indicate variable
conditions[${#conditions[@]}]="$token"; fi
}
##! @brief Helper for write_as_php, cleans up the conditionals ##! @returns Error code according to @ref return_values. ##! @pre Expects the inherited $condition variable to be populated with the condition to sanitize. ##! @post Updates the inherited $condition variable with the sanitized version. ##! ##! Removes bash brackets around condition. ##! The behaviour is too tightly coupled to the writing of php for this to be part of tha analysis ##! stage, in practise this function is doing both analysis and writing. function sanitize_condition { #we start by parsing the complete condition in parts local -a conditions;
parse_condition <<< "${condition}";
condition="";
locali=0; local partial_condition=""; while [ "$i"-lt ${#conditions[@]} ]; do if [ "${conditions[$i]}"=="o" ]; then local operator="${conditions[$((i+1))]}";
case "$operator" in "&&"|"||") #rewrite regex if [[ "$partial_condition"=~ ; then
partial_condition="preg_match(\"${BASH_REMATCH[2]}\",${BASH_REMATCH[1]})"; fi condition+="$partial_condition $operator " partial_condition="" ;; *) partial_condition+=" ${operator} "; ;; esac fi if [ "${conditions[$i]}" == "v" ]; then local variable="${conditions[$((i+1))]}"; if [[ "$variable" =~ (!+[[:blank:]]+)([[:alnum:]_]+)(.*) ]]; then local negation; negation="${BASH_REMATCH[1]}"; #the variable will be put throught the parser, so temporarily store the composition somewhere #else, same goes for the index i. local -a temp_composition; local -i temp_i; #shellcheck disable=SC2124 #temp_composition is an array, not a string temp_composition=${composition[@]}; temp_i=$i; { local -a composition; local -i i; i=0; parse <<< "${BASH_REMATCH[2]}" variable=$(write_as_php 0); } #restore the composition #shellcheck disable=SC2124 #composition is an array, not a string composition=${temp_composition[@]}; i=$temp_i; #the parser has appended a closing semicolon, so remove that. variable="${variable::-1}"; #and prepend the negation if present. variable="$negation$variable"; else sanitize_var "$variable"; fi partial_condition+="${variable}"; fi i=$((i+2)); done #rewrite regex if [[ "$partial_condition" =~ (.*)[[:blank:]]+=~[[:blank:]]+(.*)[[:blank:]]* ]]; then partial_condition="preg_match(\"${BASH_REMATCH[2]}\",${BASH_REMATCH[1]})"; fi ${!scenario@} condition+="$partial_condition"; return $C_SUCCESS;}##! @brief Sanitize a variable##! @param $arg1 Variable to sanitize##! @returns Error code according to @ref return_values.##! @post Updates inherited $variable to the sanitized value.##!##! The behaviour is too tightly coupled to the writing of php for this to be part of tha analysis##! stage, in practise this function is doing both analysis and writing.function sanitize_var { variable="$1"; if [[ "$variable" =~ (.*)\$\{#(.*)\[@\]\}(.*) ]]; then variable="${BASH_REMATCH[1]}count(${BASH_REMATCH[2]})${BASH_REMATCH[3]}"; fi if [[ "$variable" =~ (.*)\$\{#(.*)\}(.*) ]]; then variable="${BASH_REMATCH[1]}strlen(${BASH_REMATCH[2]})${BASH_REMATCH[3]}"; fi if [[ "$variable" =~ (.*)\$\(\((.*)\)\)(.*) ]]; then variable="${BASH_REMATCH[1]}(${BASH_REMATCH[2]})${BASH_REMATCH[3]}"; fi #rewrite indirect @todo what is the equivalent in php? $$?? if [[ "$variable" =~ (.*)\$\{!(.*)@\}(.*) ]]; then variable="array_filter(get_defined_vars(), function(\$value){return str_starts_with(\$value, '${BASH_REMATCH[2]}');});"; fi if [[ "$variable" =~ ^\$#$ ]]; then ##! @todo test this variable="func_num_args()"; fi return $C_SUCCESS;}##! @brief Sanitize an assignment (right hand side argument of assignment).##! @param $arg1 Assignment to sanitize##! @returns Error code according to @ref return_values.##! @post Updates inherited $assignment to the sanitized value.function sanitize_assignment { assignment="$1"; if [[ "$assignment" =~ (.*)\$\(\((.*)\)\)(.*) ]]; then #remove $(( from start and )) from end assignment="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}"; fi local -a stack; while [ "${#assignment}" -gt 0 ]; do if [[ "$assignment" =~ ^(\s*)([[:alnum:]_]*)(\s*)([\+-/\*%]).* ]]; then local -i length; length=$((${#BASH_REMATCH[1]}+${#BASH_REMATCH[2]}+${#BASH_REMATCH[3]}+${#BASH_REMATCH[4]})); stack[${#stack[@]}]="${BASH_REMATCH[2]}"; stack[${#stack[@]}]="${BASH_REMATCH[4]}"; assignment=${assignment: $length}; else stack[${#stack[@]}]="$assignment"; assignment=""; fi done local variable; #put variable indications in front of the variables and sanitize the variable. for variable in "${stack[@]}"; do case "${variable::1}" in "+"|"-"|"*"|"/"|[0-9]) ;;#do nothing *) { sanitize_var "$variable"; };; esac assignment="$assignment$variable"; done}##! @brief clean the switch option value and replace single | or operator with phps dual ||.##! @param $arg1 The option string to sanitize.##! @pre Expects to inherit clean variable named option to polate with the sanitized option.function sanitize_option { input="$1"; local -i i=0; option=""; local -i in_string=0; while [ $i -lt ${#input} ]; do local char="${input:$i:1}"; option+="$char"; if [ "$char" = "\"" ]; then if [ "$in_string"-eq 0 ]; then in_string=1; else in_string=0; fi i=$((i+1)); continue; fi if [ "$in_string"-eq 1 ]; then i=$((i+1)); continue; fi #replace single or operator from bash with the dual from php if [ "$char" = "|" ]; then
option+="|"; fi i=$((i+1)); done
}
##! @brief Write the php ##! @param $arg1 Include the @c &lt;?php and @c ?&gt; tags. If value is 1 then the tags are included, ## otherwise they are skipped. ##! @returns Error code (for now always success) according to @ref return_values. ##! ##! Writes the inherited composition as php to stdout. function write_as_php { local -i include_php_tags; local -i linenumber=0;
include_php_tags="$1";
if [ "$include_php_tags"-eq 1 ]; then printf"<?php"; fi ##! Variable for iterating over the composition. local -i i=0;
while [ "$i"-lt ${#composition[@]} ]; do
case ${composition[$i]} in "!") { #shebang printf"// #!%s" "${composition[$((i+1))]}"; i=$((i+1)); };; "d") { #doxygen comment printf "//!%s" "${composition[$((i+1))]}"; i=$((i+1)); };; "#") { #comment printf "// %s" "${composition[$((i+1))]}"; i=$((i+1)); };; "n") { #newline printf "\n"; linenumber=$((linenumber+1));
};; "v") { #variable printf""; if [ "${composition[$((i+1))]}"-eq 1 ]; then printf"const "; fi printf"$%s""${composition[$((i+3))]}";#name #cannot use type, or should we autoprepend with /**@var %s*/ "${composition[$((i+2))]}"? if [ "${composition[$((i+4))]}" != "" ]; then #clean up the assigned value local variable; #needed by sanitize_var (sets its return value there).
sanitize_var "${composition[$((i+4))]}"; printf" = %s""$variable";#value fi printf";"; i=$((i+4));
};; "f") { printf"function "; printf"%s(""${composition[$((i+1))]}";#name local -i j; j=0; while [ "$j"-lt"${composition[$((i+2))]}" ]; do j=$((j+1)); printf"\$arg%d""$j";#argument<index> if [ "$j"-lt"${composition[$((i+2))]}" ]; then printf", "; fi done printf"): int {"; ## @todo add parameters & conditional typehint (void if no return??) i=$((i+2));
};; "{") { printf"{";
};; "}") { printf"}";
};; "c") { #command / function call printf"%s""${composition[$((i+1))]}(";#name local -i nargs="${composition[$((i+2))]}";#number of arguments local -i j; j=1; while [ "$j"-le"$nargs" ]; do local variable; #needed by sanitize_var (sets its return value there).
sanitize_var "${composition[$((i+2+j))]}"; printf"%s""$variable"; #arguments if [ "$j"-lt"$nargs" ]; then printf", "; fi j=$((j+1)); done printf");"; i=$((i+2+nargs));
};; "j") { #command / function call local keyword="${composition[$((i+1))]}"; local condition="${composition[$((i+2))]}"; #sanitize the condition
sanitize_condition;
case "$keyword" in "if") { printf"if ( %s ) {""$condition";
};; "elif") { printf"} else if ( %s ) {""$condition";
};; "else") { printf"} else {";
};; "fi"|"done"|"esac") { printf"}";
};; "while") { printf"while ( %s ) {""$condition";
};; "case") { printf"switch ( %s ) {""$condition";
};; "continue"|"break") { printf"%s;""$keyword";
};; "for") { printf"for (%s) {""$condition";
};;
*) echo"Unknown flow control at write: \"$keyword\"" 1>&2; return $C_WRITE_ERROR;;
esac i=$((i+2));
};; "r") { #return printf"%s;""${composition[$((i+1))]}"; ##! @todo When parsing, exclude trailing ; i=$((i+1));
};; "=") { #assignment local variable; #needed by sanitize_var (sets its return value there).
sanitize_var "${composition[$((i+1))]}"; local assignment; #needed by sanitize_assignment (sets its return value there).
sanitize_assignment "${composition[$((i+3))]}";
printf"$%s %s %s;""$variable""${composition[$((i+2))]}""$assignment"; i=$((i+3));
};; "w") { #whitespace printf"%s""${composition[$((i+1))]}"; i=$((i+1));
};; "o") { #switch option ##! @todo Add check on value being * and insert default: instead. ##! @todo Add check on value being multiple (using |) and insert "||" instead. local option; #needed by sanitize_option
sanitize_option "${composition[$((i+1))]}"; printf"case %s:""$option"; i=$((i+1));
};; ";") { #break statement (switch) printf"break;";
};;
*) echo"something else: |${composition[$i]}|";;
esac i=$((i+1)); done; if [ "$include_php_tags"-eq 1 ]; then printf"?>"; fi return $C_SUCCESS;
}
##! @brief Main function ##! @param $arg1 Input file to rewrite to doxygen parsable php. ##! @returns Error code according to @ref return_values. ##! ##! @todo Add flag to write to file instead of stdout (for uses outside doxgen and debugging). ##! @todo Add getopt. function main { local inputfile="$1";
local -a composition; if! parse < "$inputfile"; then echo"Parse unsuccessful" 1>&2; return $?; fi
if! analyse < "$inputfile"; then echo"Analysis unsuccessful" 1>&2; return $?; fi i=0; printf"" > composition.txt while [ "$i"-lt ${#composition[@]} ]; do printf"%3d: %s""$i""${composition[$i]}" >> composition.txt
get_composition_element_size "$i"; localsize=$?; j=0; while [ "$j"-lt"$size" ]; do printf", %s""${composition[$((i+j+1))]}" >> composition.txt; j=$((j+1)); done printf"\n" >> composition.txt i=$((i+j+1)); done if! write_as_php 1; then echo"write of php unsuccessful" 1>&2; return $?; fi return $C_SUCCESS;
}