Pieters bash scripts
Documentation for the bash scripts I have published
Loading...
Searching...
No Matches
doxygen-bash-filter.sh
Go to the documentation of this file.
1#! /bin/bash
2##! Author: Pieter van der Star (info@pietervanderstar.nl)
3##! Modifications by: (unmodified)
4##!
5##! @warning Do not use this script if your input is large and you are impatient.
6##! @copyright This program is free software: you can redistribute it and/or modify
7##! it under the terms of the GNU Affero General Public License as
8##! published by the Free Software Foundation, either version 3 of the
9##! License, or (at your option) any later version.
10##! \n
11##! This program is distributed in the hope that it will be useful,
12##! but WITHOUT ANY WARRANTY; without even the implied warranty of
13##! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14##! GNU Affero General Public License for more details.
15##! \n
16##! You should have received a copy of the GNU Affero General Public License
17##! along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19#Changelog:
20#┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━┓
21#┃ 2 nov 2023 │ First header information in place │ Pieter van der Star ┃
22#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
23#┃ 9 nov 2023 │ First release │ Pieter van der Star ┃
24#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
25#┃ 10 nov 2023 │ - Added support for function call │ Pieter van der Star ┃
26#┃ │ detection when called in conditional. │ ┃
27#┃ │ - Added function call with piping using │ ┃
28#┃ │ '<' where the pipe operator and operand│ ┃
29#┃ │ are not recognised as arguments. │ ┃
30#┃ │ - Documented the last undocumented │ ┃
31#┃ │ functions. │ ┃
32#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
33#┃ 22 dec 2023 │ - Added more function documentation │ Pieter van der Star ┃
34#┃ │ - Removed unused function │ ┃
35#┃ │ consume_comment. │ ┃
36#┃ │ - Extended operator support in │ ┃
37#┃ │ parse_assignment. │ ┃
38#┃ │ - Minor style improvements. │ ┃
39#┃ │ - Fixed bug where assignments were │ ┃
40#┃ │ sometimes rewritten as 0 or 1 │ ┃
41#┃ │ (depending on the const status). │ ┃
42#┃ │ - Added argument flags and argument │ ┃
43#┃ │ checking. │ ┃
44#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
45#┃ 20 feb 2024 │ - Added support for "name() {" fuctions. │ Pieter van der Star ┃
46#┃ │ - Improved documentation. │ ┃
47#┃ │ - Removed debug code │ ┃
48#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
49#┃ 22 feb 2024 │ - Added translation for $# into │ Pieter van der Star ┃
50#┃ │ func_num_args(). │ ┃
51#┃ │ - Fixed bug where $ was appended to ints │ ┃
52#┃ │ in arithmatic in assignement. │ ┃
53#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
54#┃ 24 feb 2024 │ - Fixed a lot of small bugs after testing│ Pieter van der Star ┃
55#┃ │ on larger code base (my own scripts). │ ┃
56#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
57#┃ 25 feb 2024 │ - Revized conditional handling. │ Pieter van der Star ┃
58#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
59#┃ 29 feb 2024 │ - Added support for heredoc in │ Pieter van der Star ┃
60#┃ │ conditional handling. │ ┃
61#┃ │ - Added trailing newline to │ ┃
62#┃ │ update_function error message. │ ┃
63#┃ │ - Added for loop support. │ ┃
64#┃ │ - Added support for indirect expansion in│ ┃
65#┃ │ variables. │ ┃
66#┃ │ - Added support for hyphen ('-') in │ ┃
67#┃ │ command name. │ ┃
68#┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
69
70##! @file
71##! @brief This script transforms bash scripts into php-ish that Doxygen can parse.
72##!
73##! @todo Review function documentation.
74##! @todo Add support for [[ regex without if.
75##! @todo Add support for "source <script>".
76##! @todo Add support for line continuation ("\" followed by newline).
77##! @todo Add return type other than int if the function doesn't contain a return statement. (Also handle return void.)
78##! @todo Count the newlines in multiline condition (when using heredocs) for linenumber in error messages.
79##! @bug The line number for variable errors (such as incorrect flag to declare) seems to be one line too early.
80##! @bug When the source document is updated while the filter is running the filter sometimes ends
81##! up in an endless loop showing the error "Unknown item in get_composition_element_size".
82##!
83##! This script works by parsing the Bash script file and then outputting matching PHP code on
84##! stdout. The parsing stores each tokens in one big flat array. This array is called
85##! composition. To identify each kind of token the first element is an identifier, which
86##! determines how many elements follow. Thy are stored as listed in the table below:
87##! |Purpose |Identifier|Elements[1]| Purpose of element |
88##! |:-----------------------|:---------|----------:|:----------------------------------------|
89##! |shebang |! | 1| Contents of the shebang |
90##! |Doxygen comment |d | 1| Contents of the comment |
91##! |comment |# | 1| Contents fo the comment |
92##! |newline |n | -| |
93##! |variable |v | 1| Indicates readonly variable |
94##! | | | 2| Type of the variable |
95##! | | | 3| Name of the variable |
96##! | | | 4| Assigned value, or empty if not assigned|
97##! |function |f | 1| Name of the function |
98##! | | | 2| Number of parameters, can be 0 if none |
99##! | | | >2 (odd)| Name of the parameter |
100##! | | | >2 (even)| Type of the parameter |
101##! |scope opening |{ | -| |
102##! |scope closing |} | -| |
103##! |command/function call |c | 1| The command |
104##! | | | 2| Number of arguments, can be 0 if none |
105##! | | | >2| Parameter values |
106##! |flow control |j | 1| Type of control (if, while else etc) |
107##! | | | 2| The condition |
108##! |return statement |r | -| |
109##! |variable assignment |= | 1| The operand |
110##! | | | 2| The operator |
111##! | | | 3| The value |
112##! |whitespace |w | 1| The value |
113##! |switch option |o | 1| The value |
114##! |break statement (;;) | | -| |
115##! @b 1 Elements is the index of the element after the identifier.
116##! @note The matching code may not be valid php but is good enough for Doxygen to parse.
117##! @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.
118
119##!
120##! The script works in three stages:
121##! -# Parsing
122##! -# Analysis
123##! -# Writing
124##! \n
125##! Parsing extracts the meaningful data from the script file.\n
126##! Analysis enhances that data.\n
127##! Writing outputs the data.
128##!
129##! This code is written with readibility as first priority (after the "it should do its job"
130##! requirement).
131##!
132##! # How to use:
133##! -# Add this file to the projects root directory (where your doxyfile lives).
134##! -# Update some of the doxyfile values.
135##! -# First tell Doxygen to document .sh files as if it is a php file:
136##! <tt>EXTENSION_MAPPING = sh=php</tt>.
137##! -# Then Doxygen needs to know about this filter:
138##! <tt>FILTER_PATTERNS = *.sh=./doxygen-bash-filter.sh</tt>.
139##! -# add .sh to the FILE_PATTERNS list.
140##! -# Optionally add doxygen-bash-filter.sh to the EXCLUDE values.
141##! <tt>EXCLUDE = doxygen-bash-filter.sh</tt>.
142##! @note When @c EXTENSION_MAPPING, @c FILTER_PATTERNS or @c EXCLUDE is already set add the values to the
143##! the list.
144
145##! @defgroup return_values Return values
146##! @brief List of return values used in this script.
147##! @{
148
149##! Return value for success.
150declare -i -r C_SUCCESS=0;
151##! Return value on parse error.
152declare -i -r C_PARSE_ERROR=1;
153##! Return value on error when analysing parsed composition.
154declare -i -r C_ANALYSIS_ERROR=2;
155##! Return value on error when writing result.
156declare -i -r C_WRITE_ERROR=3;
157
158##! @}
159
160 ##############################################################################################
161 ### ###
162 ### Parsing ###
163 ### ###
164 ##############################################################################################
165
166##! @brief This is a function just to test the function declaration option not used in the rest of this document.
167test() {
168 echo "This is a function and here just as a test";
169}
170##! @brief This is a function just to test the function declaration option not used in the rest of this document.
171test2 () {
172 echo "This is a function and here just as a test";
173}
174
175##! @brief This is a function just to test the function declaration option not used in the rest of this document.
176function test3() {
177 echo "This is a function and here just as a test";
178}
179
180##! @brief Reads the next character from the input stream
181##! @post Sets inherited $char with the next character, unless it is an escaped char in which case it.
182##! @post Sets the char variable to two chars ( '\' and <char>).
183function get_next_char {
184 read -N 1 -r char;
185 if [ "$char" == "\\" ]; then
186 read -N 1 -r second_char;
187 char="$char$second_char"
188 fi
189}
190##! @brief Removes the leading and trailing double quotes " from the given string.
191##! @param $arg1 Name of the string variable to remove the quotes from
192##! @post The string named by $arg1 has its leading and trailing double quotes removed.
194 local -n string="$1";
195 if [[ "$string" =~ \"(\$[[:alnum:]_]+)\" ]]; then
196 string=${BASH_REMATCH[1]};
197 fi
198}
199
200##! @brief Discard whitespaces (except newline) from the input stream.
201##! @param $arg1 Should the characters be appended to token? (1 yes, !=1 no)
202##! @returns Error code according to @ref return_values.
203##! @post Sets the inherited $char to the first non-whitespace character gotten via
204##! @ref get_next_char().
205##! @post Updates inherited $token (depending on $arg1) with all characters until a closing quote is
206##! encountered.
207##! @note For the purposes of this function "\n" is not a whitespace character.
208function consume_whitespaces {
209 local -i append_token="$1";
210 if [ "$append_token" -eq 1 ]; then
211 token+="$char";
212 fi
213 while [ "$char" != "" ]; do
214 if [[ "$char" =~ [^[:blank:]] ]]; then
215 return $C_SUCCESS;
216 fi
217 if [ "$append_token" -eq 1 ]; then
218 token+="$char";
219 fi
220 get_next_char;
221 done
222 return $C_SUCCESS;
223}
224
225##! @brief Append everything to $token untill a command separator is encountered.
226##! @returns Error code according to @ref return_values.
227##! @post Updates inherited $token with all characters until a command separator is encountered.
228##! @post Sets the inherited $char to the separator character (except when "<space>#", then sets
229##! to "#".
230##! @note Separators are:
231##! - "\n"
232##! - ";"
233##! - "<non-newline whitespace>#"
234function consume_until_cmd_separator {
235 while [ "$char" != "" ]; do
236 if [ "$char" == "\"" ]; then #unescaped '"'
238 continue;
239 fi
240 if [[ "$char" == "\'" ]]; then #unescaped '''
242 continue;
243 fi
244 if [ "$char" == $'\n' ]; then #newline
245 return $C_SUCCESS;
246 fi
247 if [ "$char" == $';' ]; then #unescaped ';'
248 return $C_SUCCESS;
249 fi
250 if [[ "${token: -1}$char" =~ ^[[:blank:]]# ]]; then # whitespace followed by '#'
251 return $C_SUCCESS;
252 fi
253 token+="$char";
255 done
256 return $C_SUCCESS;
257}
258
259##! @brief Append everything to $token untill a non word character is encountered.
260##! @returns Error code according to @ref return_values.
261##! @post Updates inherited $token with all characters until a non word character is encountered.
262##! @post Sets the inherited $char to the non word character
263##! @note Word characters are:
264##! - Letters A to Z both upper and lowercase
265##! - The underscore character ("_")
267 while [[ "$char" =~ [[:alnum:]_] ]]; do
268 token+="$char";
270 done
271 return $C_SUCCESS;
272}
273##! @brief Append everything to $token untill a closing quote is encountered.
274##! @param $arg1 Should the characters be appended to $token? (1 yes, !=1 no)
275##! @returns Error code according to @ref return_values.
276##! @post Updates inherited $token (depending on $arg1) with all characters until a closing quote is
277##! encountered.
278##! @post Sets the inherited $char to the closing quote.
280 local -i append_token="$1";
281
282 local -r opening="$char";
283 if [ "$append_token" -eq 1 ]; then
284 token+="$char";
285 fi
286 while [ "$char" != "" ]; do
288 if [ "$append_token" -eq 1 ]; then
289 token+="$char";
290 fi
291 if [[ "$char" == "$opening" ]]; then
293 return $C_SUCCESS;
294 fi
295 done
296 echo "Warning: missing closing ${opening}-character @line: $linenumber." 1>&2;
297 return $C_SUCCESS;
298}
299
300##! @brief Append everything to $token until the matching closing bracket (']') is encountered.
301##! @returns Error code according to @ref return_values.
302##! @post Updates inherited $token with all characters until the matching closing bracket is
303##! encountered.
304##! @post Sets the inherited $char to the closing bracket.
306 token+="$char";
307 local -i level=1;
309 while [[ "$char" != "" && $level -gt 0 ]]; do
310 if [ "$char" == "\"" ]; then #unescaped '"'
312 continue;
313 fi
314 if [[ "$char" == "\'" ]]; then #unescaped '''
316 continue;
317 fi
318 token+="$char";
319
320 if [ "$char" == "[" ]; then
321 level=$((level+1));
322 fi
323 if [ "$char" == "]" ]; then
324 level=$((level-1));
325 fi
327 done
328 return $C_SUCCESS;
329}
330
331##! @brief Consumes all characters until a matching closing parenthesis is found.
332##! @returns Error code according to @ref return_values.
333##! @post Updates inherited $token with all characters until the matching closing bracket is encountered.
334##! @post Sets the inherited $char to the closing parenthes.
336 token+="$char";
337 local -i level=1;
339 while [[ "$char" != "" && $level -gt 0 ]]; do
340 if [ "$char" == "\"" ]; then #unescaped '"'
342 fi
343 if [ "$char" == "\'" ]; then #unescaped '''
345 fi
346 token+="$char";
347 if [ "$char" == "(" ]; then
348 level=$((level+1));
349 fi
350 if [ "$char" == ")" ]; then
351 level=$((level-1));
352 fi
354 done
355 return $C_SUCCESS;
356}
357
358##! @brief Append everything to $token untill a whitespace is encountered.
359##! @returns Error code according to @ref return_values.
360##! @post Updates inherited $token with all characters until a whitespace is encountered.
361##! @post Sets the inherited $char to the first whitespace.
364 while [ "$char" != "" ]; do
365 if [[ "$char" =~ [[:blank:]] ]]; then
366 return $C_SUCCESS;
367 fi
368 token+="$char";
370 done
371 return $C_SUCCESS;
372}
373
374##! @brief Parse the comment and add it to the inherited composition array.
375##! @returns Error code according to @ref return_values.
376##! @post Upon successful paring of the comment the inherited $token is cleared.
377##! @post Sets the inherited $char to the first character after the comment.
379 local index=${#composition[@]};
381 while [[ "$char" != "" && "$char" != $'\n' ]]; do
382 token+="$char";
384 done
385
386 if [ "${token:1:2}" == "! " ]; then #check if comment is shebang
387 composition[$index]="!"; #indicate we have shebang
388 token="${token:2}";
389 elif [ "${token:1:2}" == "#!" ]; then #check if comment is a doxygen comment
390 composition[$index]="d"; #update to a doxygen comment
391 token="${token:3}";
392 else
393 composition[$index]="#"; #indicate we have a "normal" comment
394 token="${token:1}";
395 fi
396 composition[$((index+1))]="$token";
397 token="";
398 if [ "$char" != $'\n' ]; then
399 echo "ERROR";
400 return 100
401 fi
402 return $C_SUCCESS;
403}
404
405##! @brief Parse a variable and add it to the inherited composition array.
406##! @returns Error code according to @ref return_values.
407##! @pre Expects the inherited $char to be set to the first character of the function to handle.
408##! @post Clears the inherited $token upon completion. Sets the inherited char to the first char after the variable
410 local index=${#composition[@]};
411 composition[$index]="v"; #this is a variable
412 composition[$((index+1))]=0; #1 if readonly, otherwise 0
413 composition[$((index+2))]="str"; #type
414 composition[$((index+3))]=""; #name
415 composition[$((index+4))]=""; #assignment (if any)
416
417 local -i -r S_PROPERTY=1;
418 local -i -r S_NAME=2;
419 local state=0;
420 #using parents char;
421 while [ "$char" != "" ]; do
422 case "$state" in
423 "$S_PROPERTY") {
424 case "$char" in
425 "r") composition[$((index+1))]=1;;
426 "i") composition[$((index+2))]="int";;
427 "a") composition[$((index+2))]="list";;
428 "A") composition[$((index+2))]="list";;
429 "g") ;; ##! @todo add support for global
430 "n") ;; ##! @todo how to handle this? Seems only interesting with regards to arguments which then get a reference instead of value.
431 " ") state=0;;
432 *) return $C_PARSE_ERROR;
433 esac
434 };;
435 "$S_NAME") {
436 ##! @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.
437 if [[ "$char" =~ ([[:space:]]|[\;=]) ]]; then
438 break;
439 elif [[ "$char" =~ ([^[:alnum:]_\"\$]) ]]; then
440 return $C_PARSE_ERROR;
441 fi
442 composition[$((index+3))]+="$char";
443 };;
444 *) {
445 consume_whitespaces 0;
446 if [ "$char" == "-" ]; then
447 state="$S_PROPERTY";
448 else
449 state="$S_NAME";
450 continue; #skip reading new char, process first
451 fi
452 };;
453 esac
454 get_next_char;
455 done;
456
457 if [ "$char" == "=" ]; then
458 token="";
459 get_next_char;
460 consume_until_cmd_separator;
461 composition[$((index+4))]="$token";
462 token="";
463 return $C_SUCCESS;
464 fi
465
466 token="";
467 get_next_char;
468 return $C_SUCCESS;
469}
470
471##! @brief Parse a function declaration and add it to the inherited composition array.
472##! @post Updates char to the last char of the function.
473## Start by getting the name, then just consume until the opening bracket, discarding the stuff beteen.
474function parse_function {
475 local index=${#composition[@]};
476 composition[$index]="f"; #this is a function
477 composition[$((index+1))]=""; #name
478 composition[$((index+2))]=0; #placeholder for number of parameters
479
480 #before we get the name we need to discard the whitespaces in between the function keyword and the name
481 consume_whitespaces;
482 token="";
483
484 #get the name
485 consume_until_non_word_char;
486 composition[$((index+1))]="$token";
487
488 #then just consume until the opening bracket
489 while [ "$char" != "{" ]; do
490 get_next_char;
491 done;
492 get_next_char;
493 token="";
494 return $C_SUCCESS;
495}
496
497##! @brief Parse a function or command call and add it to the inherited composition array.
498##! @returns Error code according to @ref return_values.
499##! @pre Expects inherited $token to be set to the function name.
500##! @pre Expects inherited $char to be set to the first char after the function name.
501function parse_command {
502 local index=${#composition[@]};
503 composition[$index]="c"; #this is a command/call
504 composition[$((index+1))]="$token";
505 composition[$((index+2))]=0; #number of arguments
506
507 local -i nargs;
508 nargs=0;
509 consume_whitespaces 0;
510 token="";
511 #get the parameters
512 while [ "$char" != "" ]; do
513 if [[ "$char" == "<" ]]; then #input piping operator shouldn't be considered an argument
514 consume_until_space;
515 #The thing being piped however can be considered an argument, so
516 ##! @todo conditionally add the thing being piped as argument based on command line parameter.
517 consume_until_cmd_separator;
518 token="";
519 return;
520 elif [[ "$char" =~ [[:space:]] || "$char" == ";" ]]; then
521 if [ ${#token} -gt 0 ]; then
522 if [[ "$nargs" -eq 0 && "$token" == "()" ]]; then
523 #This isn´t a command but a function definition
524 composition[$index]="f"; #update to a function
525 #index+1 can stay as is, the content is correct, name is the same token
526 #index+1 can stay as is, the content is correct for now 0 parameters
527 token="";
528 consume_whitespaces;
529 #then just consume until the opening bracket
530 while [ "$char" != "{" ]; do
531 get_next_char;
532 done;
533 get_next_char;
534 token="";
535 return $C_SUCCESS;
536 fi
537 nargs=$((nargs+1));
538 composition[$((index+2+nargs))]="$token";
539 token="";
540 fi
541 if [[ "$char" == $'\n' || "$char" == ";" ]]; then
542 break;
543 fi
544 elif [[ "$char" == "\"" || "$char" == "\'" ]]; then
546 continue;
547 else
548 token+="$char";
549 fi
551 done;
552 if [[ "$char" != $'\n' ]]; then
554 fi
555 composition[$((index+2))]="$nargs"; #number of arguments
556 return $C_SUCCESS;
557}
558
559##! @brief consumes input until the keyword ($arg1) has been found.
560##! @param $arg1 Keyword to search for.
561##! @returns Error code according to @ref return_values.
562##! @retval $C_SUCCESS if keyword found.
563##! @retval $C_PARSE_ERROR if keyword not found before end of file occurred.
564##! @post Sets the inherited $token to everything before the keyword.
565##! Sets the inherited $char to the first whitespace following the keyword.
566##! @todo handle parenthesis, if keyword is within the parenthesis it doesn't count.
568 local word="";
569 #shellcheck disable=2078 #While true by design.
570 while [ : ]; do
572 if [ "$char" == "" ]; then
573 return $C_PARSE_ERROR;
574 fi
575 if [[ "$char" =~ [[:space:]] ]]; then
576 if [ "$word" == "$1" ]; then
577 return $C_SUCCESS;
578 fi
579 token+="$word$char";
580 word="";
581 continue;
582 fi
583 word+="$char";
584 done
585}
586
587##! @brief Parse a flow control statement and add it to the inherited composition array.
588##! @returns Error code according to @ref return_values.
589##! @pre Expects the inherited $token to contain the control flow keyword.
590##! @post Clears the inherited $token upon successful return.
592 local keyword="$token"; #control keyword
593 local index=${#composition[@]};
594 composition[$index]="j"; #this is flow control (j for jumps)
595 composition[$((index+1))]="$keyword"; #control keyword
596 composition[$((index+2))]=""; #condition
597 local end_keyword="";
598
599 case "$keyword" in
600 "while"|"for") {
601 end_keyword="do";
602 };;
603 "if"|"elif") {
604 end_keyword="then";
605 };;
606 "case") {
608 return $?;
609 };;
610 "else"|"fi"|"done"|"esac"|"continue"|"break") {
611 composition[$((index+2))]=""; #condition doesn't exist
612 token="";
613 return $C_SUCCESS;
614 };;
615 *) {
616 echo "unknown flow control token |$char|" 1>&2;
617 return $C_PARSE_ERROR;
618 };;
619 esac
620 token="";
621 consume_until_keyword "$end_keyword";
622
623 composition[$((index+2))]="$token"; #condition
624 consume_whitespaces 0;
625
626 token="";
627 return $C_SUCCESS;
628}
629
630##! @brief Gets the condition of the switch.
631##! @returns Error code (for now always success) according to @ref return_values.
632##! @post Updates the condition field in the composition element.
633function parse_case {
634 #get the switch condition
635 token="";
636 while [[ ! "$token" =~ [[:blank:]]?(.*)[[:blank:]]+in ]]; do
637 consume_until_cmd_separator;
638 done
639 token="${BASH_REMATCH[1]}"; #remove the " in" part
640 strip_quotes_if_variable "token";
641 composition[$((index+2))]="$token"; #condition
642 token="";
643 return $C_SUCCESS;
644}
645
646##! @brief Gets the value of the switch case.
647##! @returns Error code according to @ref return_values.
648##! @post Adds the case option to the composition.
649function parse_case_option {
650 local index=${#composition[@]};
651 composition[$index]="o"; #this is a switch option (o for option)
652 if [[ "$token" =~ (.*)\‍) ]]; then
653 composition[$((index+1))]="${BASH_REMATCH[1]}"; #option value
654 else
655 return $C_PARSE_ERROR;
656 fi
657 return $C_SUCCESS;
658}
659
660##! @brief Extract variable name and value from assignment statements.
661##! @returns Error code (for now always success) according to @ref return_values.
662##! @pre Expects the inhertied $token to be filled with the value being assigned.
663##! @post Clears the inherited $token.
664##!
665##! Adds the assignment information to the composition.
666function parse_assignment {
667 local index=${#composition[@]};
668 composition[$index]="="; #this is an assignment
669 token+="$char";
670 case "${token: -2}" in
671 "+="|"-="|"*="|"/="|"%="|"<<="|">>="|"&="|"^="|"|=") {
672 composition[$((index+1))]="${token::-2}"; #operand
673 composition[$((index+2))]="${token: -2}"; #operator
674 };;
675 *){
676 composition[$((index+1))]="${token::-1}"; #operand
677 composition[$((index+2))]="${token: -1}"; #operator
678 };;
679 esac
680 token="";
681 get_next_char;
682 consume_until_cmd_separator;
683 composition[$((index+3))]="$token"; #value
684
685 token="";
686
687 return $C_SUCCESS;
688}
689
690##! @brief Parses an sh file into a composition.
691##! @returns Error code (for now always success) according to @ref return_values.
692##!
693#!! Reads tokens from the input pipe and parses this into a composition containing the flow of
694##! the input script.
695##! Call like <tt>parse < "inputfile.sh"</tt>.
696function parse {
697 IFS='';
698 local token="";
699 local char;
700 local -i linenumber=0;
701 get_next_char;
702 #shellcheck disable=2078 #While true by design.
703 while [ : ]; do
704 if [[ "$token$char" =~ ^[[:blank:]]+[^[:blank:]]$ ]]; then
705 composition[${#composition[@]}]="w"; #indicate we have whitespace
706 composition[${#composition[@]}]="$token"; #add the contents
707 token="";
708 fi
709
710 #if [ "$char" == "(" ]; then
711 # consume_until_closing_parenthesis;
712 # continue;
713 #fi
714 if [ "$char" == "[" ]; then
715 consume_until_closing_square_bracket;
716 continue;
717 fi
718 if [[ "$char" == "\"" || "$char" == "\'" ]]; then
719 consume_until_closing_quote 1;
720 continue;
721 fi
722
723 if [[ "$token$char" =~ ^#.* ]]; then
724 token+="$char";
725 if ! parse_comment; then
726 echo "Comment error @line: $linenumber" 1>&2;
727 return $C_PARSE_ERROR;
728 fi
729 elif [[ "$token$char" =~ ^declare[[:blank:]]$ ]]; then
730 if ! parse_variable; then
731 echo "Variable error @line: $linenumber" 1>&2;
732 return $C_PARSE_ERROR;
733 fi
734 elif [[ "$token$char" =~ ^local[[:blank:]]$ ]]; then
735 if ! parse_variable; then
736 echo "Variable error @line: $linenumber" 1>&2;
737 return $C_PARSE_ERROR;
738 fi
739 elif [[ "$token" == $'\n' ]]; then
740 composition[${#composition[@]}]="n"; #indicate we have a newline
741 #increment the linecount
742 linenumber=$((linenumber+1))
743 ##! @todo make this a switch for use during debugging?
744 #echo "Parsing now at line: $linenumber" 1>&2;
745 token="";
746 elif [ "$token" == $';' ]; then
747 #check the next char to detect a break ";;".
748 if [ "$char" == $';' ]; then
749 composition[${#composition[@]}]=";"; #indicate we have a break statement
750 token="";
751 get_next_char;
752 fi
753 #just skip over the command separator (if just ;)
754 token="";
755 elif [[ "$token$char" =~ ^function[[:blank:]]$ ]]; then
756 if ! parse_function; then
757 echo "Function error @line: $linenumber" 1>&2;
758 return $C_PARSE_ERROR;
759 fi
760 elif [[ "$token" =~ ^[\{\}]$ ]]; then
761 composition[${#composition[@]}]="$token"; #indicate we have an opening of a scope
762 token="";
763 elif [[ "$token" =~ ^return[[:blank:]]$ ]]; then
764 composition[${#composition[@]}]="r"; #indicate we have a return statement
765 consume_until_cmd_separator;
766 ##! @todo why do we have an extra space between return and value?
767 composition[${#composition[@]}]="$token";
768 token="";
769 elif [[ "$char" == "=" ]]; then
770 if ! parse_assignment; then
771 echo "Assignment error @line: $linenumber" 1>&2;
772 return $C_PARSE_ERROR;
773 fi
774 continue;
775 elif [[ "$token" =~ ^[^\‍(]*\‍)$ ]]; then
776 parse_case_option;
777 token="";
778 #elif [[ "$token$char" =~ ([[:alnum:]_]*)[[:space:]]*\‍([[:space:]]*\‍) ]]; then
779 #echo "Func? \"$token\"";
780 #if ! parse_function "${BASH_REMATCH[1]}"; then
781 # echo "Function error" 1>&2;
782 # return $C_PARSE_ERROR;
783 #fi
784 elif [[ "$token$char" =~ ^[[:alnum:]_\.\/-]+[^[:alnum:]_\.\/-]$ || "$char" == "" || $char == ";" ]]; then
785 case "$token" in
786 "while"|"for"|"done"|"if"|"elif"|"else"|"fi"|"case"|"esac"|"continue"|"break") {
787 if ! parse_flow_control; then
788 echo "flow control error @line: $linenumber" 1>&2;
789 return $C_PARSE_ERROR;
790 fi
791 continue;
792 };;
793 esac
794
795 if ! parse_command; then
796 echo "Command error @line: $linenumber" 1>&2;
797 return $C_PARSE_ERROR;
798 fi
799 fi
800
801 token+="$char";
802 if [ "$char" == "" ]; then
803 break;
804 fi
805 get_next_char;
806 done;
807
808 if [ ${#token} -ne 0 ]; then
809 echo "token not empty. Still containts: \"$token\"" 1>&2;
810 i=0;
811 printf "ASCII values: " 1>&2;
812 while [ "$i" -lt "${#token}" ]; do
813 printf " %d" "'${token:$i:1}" 1>&2;
814 i=$((i+1));
815 done
816 printf "\n" 1>&2;
817 return $C_PARSE_ERROR;
818 fi
819 return $C_SUCCESS;
820}
821
822##! @brief Calculate the offset of the next element in components based on the current one.
823##! @param $arg1 Index of the composition element to get the offset for.
824##! @returns The offset for this element.
825function get_composition_element_size {
826 local -i i;
827 i="$1";
828 local identifier="${composition[$i]}";
829 case "$identifier" in
830 "n"|"{"|"}"|";") { #skip next 0 elements (no skip)
831 return 0;
832 };;
833 "!"|"d"|"#"|"r"|"w"|"o") { # skip next 1 elements
834 return 1;
835 };;
836 "j"|"f") {
837 return 2;
838 };;
839 "=") {
840 return 3;
841 };;
842 "v") {
843 return 4;
844 };;
845 "c") { #Skip dynamic for command/function call
846 local -i nargs="${composition[$((i+2))]}";#number of arguments
847 return "$((nargs+2))";
848 };;
849 *) {
850 echo "Unknown item in get_composition_element_size |$identifier|" 1>&2;
851 #no good value to return, but as the result is often used for increment return 1 so we
852 #get forward
853 return 1;
854 };;
855 esac
856}
857
858 ##############################################################################################
859 ### ###
860 ### Analysis ###
861 ### ###
862 ##############################################################################################
863
864##! @brief Extract function arguments ($n) and updates the function accordingly.
865##! @param $arg1 Index of the fuction in the composition.
866##! @returns Error code according to @ref return_values (for now always success).
867##!
868##! Updates the function in the composition.
869function update_function {
870 local -i composition_index;
871 composition_index="$1";
872
873 local -i i="$composition_index";
874 local num_args=0;
875 local -i level=1;
876
877 #Search through the functions scope for any variable set to a parameter.
878 #shellcheck disable=SC2078 #Intentional always true condition. Using breaks to terminate loop
879 while [ : ]; do
880 if [ "$i" -gt "${#composition[@]}" ]; then
881 printf "ERROR: Encountered end of file before scope has been closed. \n\tNested level: %d.\n" "$level" 1>&2;
882 return $C_ANALYSIS_ERROR;
883 fi
884 get_composition_element_size "$i";
885 offset=$?;
886 i=$((i+offset+1));
887
888 if [ "${composition[$i]}" == "{" ]; then #handle deepen scope
889 level=$((level+1));
890 continue;
891 fi
892 if [ "${composition[$i]}" == "}" ]; then #handle "undeepen" scope
893 level=$((level-1));
894 if [ "$level" -eq 0 ]; then
895 break;
896 fi
897 continue;
898 fi
899
900 if [ "${composition[$i]}" == "c" ]; then # "decode" variable declaration
901 local -i num_parameters="${composition[$((i+2))]}";
902 local -i j=0;
903 while [ "$j" -lt "$num_parameters" ]; do
904 argument="${composition[$((i+3+j))]}"; #argument to called function/program
905 if [[ "$argument" =~ \"?\$([[:digit:]]*) ]]; then
906 local -i argument_index="${BASH_REMATCH[1]}";
907 if [ "$num_args" -lt "$argument_index" ]; then
908 num_args="$argument_index";
909 fi
910 fi
911 j=$((j+1));
912 done
913 fi
914 if [ "${composition[$i]}" == "v" ]; then # "decode" variable declaration
915 local variable="${composition[$((i+4))]}";
916 fi
917 if [ "${composition[$i]}" == "=" ]; then #"decode" variable assignment
918 local variable="${composition[$((i+3))]}";
919 fi
920 if [[ "$variable" =~ \"?\$([[:digit:]]*) ]]; then
921 local -i argument_index="${BASH_REMATCH[1]}";
922 if [ "$num_args" -lt "$argument_index" ]; then
923 num_args="$argument_index";
924 fi
925 fi
926 done;
927 composition[$((composition_index+2))]="$num_args";
928 return $C_SUCCESS;
929}
930
931##! @brief Analyses the composition and updates information if required.
932##! @returns Error code (for now always success) according to @ref return_values.
933##! @todo Add error checking where applicable.
934function analyse {
935 local -i i=0;
936 while [ "$i" -lt ${#composition[@]} ]; do
937 if [ "${composition[$i]}" == "f" ]; then
938 update_function "$i";
939 fi
940
941 get_composition_element_size "$i";
942 offset=$?;
943 i=$((i+offset+1));
944 done;
945 return $C_SUCCESS;
946}
947
948 ##############################################################################################
949 ### ###
950 ### Write ###
951 ### ###
952 ##############################################################################################
953
954##! @brief Helper for sanitize_condition, parses the condition into variables and operators
955function parse_condition {
956 local token="";
957 local char;
958
959 #shellcheck disable=2078 #While true by design.
960 while [ : ]; do
961 get_next_char
962 #echo "char >${char}<" 1>&2;
963 if [ "$char" == "" ]; then
964 break;
965 fi
966 if [[ "$token$char" =~ ^(\[)+[[:space:]] ]]; then
967 #ignore the opening brackets
968 token="";
969 continue;
970 fi
971 if [[ "$token$char" =~ ^(\[|\])+[[:space:]] ]]; then
972 #ignore the closing brackets
973 token="";
974 continue;
975 fi
976 if [[ "$token$char" =~ ^[[:space:]] ]]; then
977 consume_whitespaces;
978 token="";
979 fi
980 if [[ "$char" == "\"" || "$char" == "\'" ]]; then
982 fi
983 if [[ "$token$char" =~ ^(.*)[[:blank:]]+(-[[:alpha:]]+|==|\!=|=~|&&|\|\||\!|\]+;)[[:space:]$] ]]; then
984 local condition="${BASH_REMATCH[1]}";
985 local operator="${BASH_REMATCH[2]}";
986
987 if [ "${#condition}" -gt 0 ]; then
988 conditions[${#conditions[@]}]="v"; #indicate variable
989 conditions[${#conditions[@]}]="$condition";
990 fi
991 if [[ "$operator" != "];" && "$operator" != "]];" ]]; then #ignore the closing brackets
992 conditions[${#conditions[@]}]="o"; #indicate operator
993 conditions[${#conditions[@]}]="$operator";
994 fi
995 token="";
996 if [ "$char" == "]" ]; then
997 token="]";
998 fi
999 continue;
1000 fi
1001 token+="$char";
1002 done
1003
1004 #As heredoc always ends with a newline we need to remove that.
1005 token=${token::-1}
1006 if [ ${#token} -gt 0 ]; then
1007 conditions[${#conditions[@]}]="v"; #indicate variable
1008 conditions[${#conditions[@]}]="$token";
1009 fi
1010
1011}
1012
1013##! @brief Helper for write_as_php, cleans up the conditionals
1014##! @returns Error code according to @ref return_values.
1015##! @pre Expects the inherited $condition variable to be populated with the condition to sanitize.
1016##! @post Updates the inherited $condition variable with the sanitized version.
1017##!
1018##! Removes bash brackets around condition.
1019##! The behaviour is too tightly coupled to the writing of php for this to be part of tha analysis
1020##! stage, in practise this function is doing both analysis and writing.
1022 #we start by parsing the complete condition in parts
1023 local -a conditions;
1024
1025 parse_condition <<< "${condition}";
1026 condition="";
1027
1028 local i=0;
1029 local partial_condition="";
1030 while [ "$i" -lt ${#conditions[@]} ]; do
1031 if [ "${conditions[$i]}" == "o" ]; then
1032 local operator="${conditions[$((i+1))]}";
1033 case "$operator" in
1034 "&&"|"||")
1035 #rewrite regex
1036 if [[ "$partial_condition" =~ (.*)[[:blank:]]+=~[[:blank:]]+(.*)[[:blank:]]* ]]; then
1037 partial_condition="preg_match(\"${BASH_REMATCH[2]}\",${BASH_REMATCH[1]})";
1038 fi
1039 condition+="$partial_condition $operator "
1040 partial_condition=""
1041 ;;
1042 *)
1043 partial_condition+=" ${operator} ";
1044 ;;
1045 esac
1046 fi
1047 if [ "${conditions[$i]}" == "v" ]; then
1048 local variable="${conditions[$((i+1))]}";
1049 if [[ "$variable" =~ (!+[[:blank:]]+)([[:alnum:]_]+)(.*) ]]; then
1050 local negation;
1051 negation="${BASH_REMATCH[1]}";
1052 #the variable will be put throught the parser, so temporarily store the composition somewhere
1053 #else, same goes for the index i.
1054 local -a temp_composition;
1055 local -i temp_i;
1056 #shellcheck disable=SC2124 #temp_composition is an array, not a string
1057 temp_composition=${composition[@]};
1058 temp_i=$i;
1059
1060 {
1061 local -a composition;
1062 local -i i;
1063 i=0;
1064 parse <<< "${BASH_REMATCH[2]}"
1065 variable=$(write_as_php 0);
1066 }
1067 #restore the composition
1068 #shellcheck disable=SC2124 #composition is an array, not a string
1069 composition=${temp_composition[@]};
1070 i=$temp_i;
1071
1072 #the parser has appended a closing semicolon, so remove that.
1073 variable="${variable::-1}";
1074 #and prepend the negation if present.
1075 variable="$negation$variable";
1076 else
1077 sanitize_var "$variable";
1078 fi
1079 partial_condition+="${variable}";
1080 fi
1081
1082 i=$((i+2));
1083 done
1084 #rewrite regex
1085 if [[ "$partial_condition" =~ (.*)[[:blank:]]+=~[[:blank:]]+(.*)[[:blank:]]* ]]; then
1086 partial_condition="preg_match(\"${BASH_REMATCH[2]}\",${BASH_REMATCH[1]})";
1087 fi
1088
1089 ${!scenario@}
1090 condition+="$partial_condition";
1091
1092 return $C_SUCCESS;
1093}
1094
1095##! @brief Sanitize a variable
1096##! @param $arg1 Variable to sanitize
1097##! @returns Error code according to @ref return_values.
1098##! @post Updates inherited $variable to the sanitized value.
1099##!
1100##! The behaviour is too tightly coupled to the writing of php for this to be part of tha analysis
1101##! stage, in practise this function is doing both analysis and writing.
1102function sanitize_var {
1103 variable="$1";
1104 if [[ "$variable" =~ (.*)\$\{#(.*)\[@\]\}(.*) ]]; then
1105 variable="${BASH_REMATCH[1]}count(${BASH_REMATCH[2]})${BASH_REMATCH[3]}";
1106 fi
1107
1108 if [[ "$variable" =~ (.*)\$\{#(.*)\}(.*) ]]; then
1109 variable="${BASH_REMATCH[1]}strlen(${BASH_REMATCH[2]})${BASH_REMATCH[3]}";
1110 fi
1111
1112 if [[ "$variable" =~ (.*)\$\‍(\‍((.*)\‍)\‍)(.*) ]]; then
1113 variable="${BASH_REMATCH[1]}(${BASH_REMATCH[2]})${BASH_REMATCH[3]}";
1114 fi
1115 #rewrite indirect @todo what is the equivalent in php? $$??
1116 if [[ "$variable" =~ (.*)\$\{!(.*)@\}(.*) ]]; then
1117 variable="array_filter(get_defined_vars(), function(\$value){return str_starts_with(\$value, '${BASH_REMATCH[2]}');});";
1118 fi
1119 if [[ "$variable" =~ ^\$#$ ]]; then ##! @todo test this
1120 variable="func_num_args()";
1121 fi
1122
1123 return $C_SUCCESS;
1124}
1125
1126##! @brief Sanitize an assignment (right hand side argument of assignment).
1127##! @param $arg1 Assignment to sanitize
1128##! @returns Error code according to @ref return_values.
1129##! @post Updates inherited $assignment to the sanitized value.
1130function sanitize_assignment {
1131 assignment="$1";
1132 if [[ "$assignment" =~ (.*)\$\‍(\‍((.*)\‍)\‍)(.*) ]]; then
1133 #remove $(( from start and )) from end
1134 assignment="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}";
1135 fi
1136 local -a stack;
1137 while [ "${#assignment}" -gt 0 ]; do
1138 if [[ "$assignment" =~ ^(\s*)([[:alnum:]_]*)(\s*)([\+-/\*%]).* ]]; then
1139 local -i length;
1140 length=$((${#BASH_REMATCH[1]}+${#BASH_REMATCH[2]}+${#BASH_REMATCH[3]}+${#BASH_REMATCH[4]}));
1141
1142 stack[${#stack[@]}]="${BASH_REMATCH[2]}";
1143 stack[${#stack[@]}]="${BASH_REMATCH[4]}";
1144 assignment=${assignment: $length};
1145 else
1146 stack[${#stack[@]}]="$assignment";
1147 assignment="";
1148 fi
1149 done
1150 local variable;
1151 #put variable indications in front of the variables and sanitize the variable.
1152 for variable in "${stack[@]}"; do
1153 case "${variable::1}" in
1154 "+"|"-"|"*"|"/"|[0-9]) ;;#do nothing
1155 *) {
1156 sanitize_var "$variable";
1157 };;
1158 esac
1159 assignment="$assignment$variable";
1160 done
1161}
1162
1163##! @brief clean the switch option value and replace single | or operator with phps dual ||.
1164##! @param $arg1 The option string to sanitize.
1165##! @pre Expects to inherit clean variable named option to polate with the sanitized option.
1166function sanitize_option {
1167 input="$1";
1168 local -i i=0;
1169 option="";
1170 local -i in_string=0;
1171 while [ $i -lt ${#input} ]; do
1172 local char="${input:$i:1}";
1173 option+="$char";
1174 if [ "$char" = "\"" ]; then
1175 if [ "$in_string" -eq 0 ]; then
1176 in_string=1;
1177 else
1178 in_string=0;
1179 fi
1180 i=$((i+1));
1181 continue;
1182 fi
1183 if [ "$in_string" -eq 1 ]; then
1184 i=$((i+1));
1185 continue;
1186 fi
1187 #replace single or operator from bash with the dual from php
1188 if [ "$char" = "|" ]; then
1189 option+="|";
1190 fi
1191 i=$((i+1));
1192 done
1193}
1194
1195
1196##! @brief Write the php
1197##! @param $arg1 Include the @c &lt;?php and @c ?&gt; tags. If value is 1 then the tags are included,
1198## otherwise they are skipped.
1199##! @returns Error code (for now always success) according to @ref return_values.
1200##!
1201##! Writes the inherited composition as php to stdout.
1202function write_as_php {
1203 local -i include_php_tags;
1204 local -i linenumber=0;
1205 include_php_tags="$1";
1206
1207 if [ "$include_php_tags" -eq 1 ]; then
1208 printf "<?php";
1209 fi
1210 ##! Variable for iterating over the composition.
1211 local -i i=0;
1212
1213 while [ "$i" -lt ${#composition[@]} ]; do
1214 case ${composition[$i]} in
1215 "!") { #shebang
1216 printf "// #!%s" "${composition[$((i+1))]}";
1217 i=$((i+1));
1218 };;
1219 "d") { #doxygen comment
1220 printf "//!%s" "${composition[$((i+1))]}";
1221 i=$((i+1));
1222 };;
1223 "#") { #comment
1224 printf "// %s" "${composition[$((i+1))]}";
1225 i=$((i+1));
1226 };;
1227 "n") { #newline
1228 printf "\n";
1229 linenumber=$((linenumber+1));
1230 };;
1231 "v") { #variable
1232 printf "";
1233 if [ "${composition[$((i+1))]}" -eq 1 ]; then
1234 printf "const ";
1235 fi
1236 printf "$%s" "${composition[$((i+3))]}";#name
1237 #cannot use type, or should we autoprepend with "${composition[$((i+2))]}"?
1238 if [ "${composition[$((i+4))]}" != "" ]; then
1239 #clean up the assigned value
1240 local variable; #needed by sanitize_var (sets its return value there).
1241 sanitize_var "${composition[$((i+4))]}";
1242 printf " = %s" "$variable";#value
1243 fi
1244 printf ";";
1245 i=$((i+4));
1246 };;
1247 "f") {
1248 printf "function ";
1249 printf "%s(" "${composition[$((i+1))]}";#name
1250 local -i j;
1251 j=0;
1252 while [ "$j" -lt "${composition[$((i+2))]}" ]; do
1253 j=$((j+1));
1254 printf "\$arg%d" "$j";#argument<index>
1255 if [ "$j" -lt "${composition[$((i+2))]}" ]; then
1256 printf ", ";
1257 fi
1258 done
1259 printf "): int {"; ## @todo add parameters & conditional typehint (void if no return??)
1260 i=$((i+2));
1261 };;
1262 "{") {
1263 printf "{";
1264 };;
1265 "}") {
1266 printf "}";
1267 };;
1268 "c") { #command / function call
1269 printf "%s" "${composition[$((i+1))]}(";#name
1270 local -i nargs="${composition[$((i+2))]}";#number of arguments
1271 local -i j;
1272 j=1;
1273 while [ "$j" -le "$nargs" ]; do
1274 local variable; #needed by sanitize_var (sets its return value there).
1275 sanitize_var "${composition[$((i+2+j))]}";
1276 printf "%s" "$variable"; #arguments
1277 if [ "$j" -lt "$nargs" ]; then
1278 printf ", ";
1279 fi
1280 j=$((j+1));
1281 done
1282 printf ");";
1283 i=$((i+2+nargs));
1284 };;
1285 "j") { #command / function call
1286 local keyword="${composition[$((i+1))]}";
1287 local condition="${composition[$((i+2))]}";
1288 #sanitize the condition
1290
1291 case "$keyword" in
1292 "if") {
1293 printf "if ( %s ) {" "$condition";
1294 };;
1295 "elif") {
1296 printf "} else if ( %s ) {" "$condition";
1297 };;
1298 "else") {
1299 printf "} else {";
1300 };;
1301 "fi"|"done"|"esac") {
1302 printf "}";
1303 };;
1304 "while") {
1305 printf "while ( %s ) {" "$condition";
1306 };;
1307 "case") {
1308 printf "switch ( %s ) {" "$condition";
1309 };;
1310 "continue"|"break") {
1311 printf "%s;" "$keyword";
1312 };;
1313 "for") {
1314 printf "for (%s) {" "$condition";
1315 };;
1316 *) echo "Unknown flow control at write: \"$keyword\"" 1>&2; return $C_WRITE_ERROR;;
1317 esac
1318 i=$((i+2));
1319 };;
1320 "r") { #return
1321 printf "%s;" "${composition[$((i+1))]}"; ##! @todo When parsing, exclude trailing ;
1322 i=$((i+1));
1323 };;
1324 "=") { #assignment
1325 local variable; #needed by sanitize_var (sets its return value there).
1326 sanitize_var "${composition[$((i+1))]}";
1327 local assignment; #needed by sanitize_assignment (sets its return value there).
1328 sanitize_assignment "${composition[$((i+3))]}";
1329
1330 printf "$%s %s %s;" "$variable" "${composition[$((i+2))]}" "$assignment";
1331 i=$((i+3));
1332 };;
1333 "w") { #whitespace
1334 printf "%s" "${composition[$((i+1))]}";
1335 i=$((i+1));
1336 };;
1337 "o") { #switch option
1338 ##! @todo Add check on value being * and insert default: instead.
1339 ##! @todo Add check on value being multiple (using |) and insert "||" instead.
1340 local option; #needed by sanitize_option
1341 sanitize_option "${composition[$((i+1))]}";
1342 printf "case %s:" "$option";
1343 i=$((i+1));
1344 };;
1345 ";") { #break statement (switch)
1346 printf "break;";
1347 };;
1348 *) echo "something else: |${composition[$i]}|";;
1349 esac
1350 i=$((i+1));
1351 done;
1352 if [ "$include_php_tags" -eq 1 ]; then
1353 printf "?>";
1354 fi
1355 return $C_SUCCESS;
1356}
1357
1358##! @brief Main function
1359##! @param $arg1 Input file to rewrite to doxygen parsable php.
1360##! @returns Error code according to @ref return_values.
1361##!
1362##! @todo Add flag to write to file instead of stdout (for uses outside doxgen and debugging).
1363##! @todo Add getopt.
1364function main {
1365 local inputfile="$1";
1366
1367 local -a composition;
1368 if ! parse < "$inputfile"; then
1369 echo "Parse unsuccessful" 1>&2;
1370 return $?;
1371 fi
1372
1373 if ! analyse < "$inputfile"; then
1374 echo "Analysis unsuccessful" 1>&2;
1375 return $?;
1376 fi
1377 i=0;
1378 printf "" > composition.txt
1379 while [ "$i" -lt ${#composition[@]} ]; do
1380 printf "%3d: %s" "$i" "${composition[$i]}" >> composition.txt
1382 local size=$?;
1383 j=0;
1384 while [ "$j" -lt "$size" ]; do
1385 printf ", %s" "${composition[$((i+j+1))]}" >> composition.txt;
1386 j=$((j+1));
1387 done
1388 printf "\n" >> composition.txt
1389 i=$((i+j+1));
1390 done
1391 if ! write_as_php 1; then
1392 echo "write of php unsuccessful" 1>&2;
1393 return $?;
1394 fi
1395 return $C_SUCCESS;
1396}
1397
1398main "$@";
consume_until_closing_parenthesis()
Consumes all characters until a matching closing parenthesis is found.
parse_variable()
Parse a variable and add it to the inherited composition array.
consume_until_space()
Append everything to $token untill a whitespace is encountered.
parse_comment()
Parse the comment and add it to the inherited composition array.
parse()
Parses an sh file into a composition.
parse_assignment()
Extract variable name and value from assignment statements.
parse_condition()
Helper for sanitize_condition, parses the condition into variables and operators.
consume_until_keyword()
consumes input until the keyword ($arg1) has been found.
get_next_char()
Reads the next character from the input stream.
parse_case()
Gets the condition of the switch.
consume_until_cmd_separator()
Append everything to $token untill a command separator is encountered.
sanitize_condition()
Helper for write_as_php, cleans up the conditionals.
consume_until_non_word_char()
Append everything to $token untill a non word character is encountered.
test3()
This is a function just to test the function declaration option not used in the rest of this document...
parse_command()
Parse a function or command call and add it to the inherited composition array.
consume_until_closing_square_bracket()
Append everything to $token until the matching closing bracket (']') is encountered.
consume_whitespaces($arg1)
Discard whitespaces (except newline) from the input stream.
parse_function()
Parse a function declaration and add it to the inherited composition array.
strip_quotes_if_variable($arg1)
Removes the leading and trailing double quotes " from the given string.
get_composition_element_size($arg1)
Calculate the offset of the next element in components based on the current one.
consume_until_closing_quote($arg1)
Append everything to $token untill a closing quote is encountered.
parse_flow_control()
Parse a flow control statement and add it to the inherited composition array.
test2()
This is a function just to test the function declaration option not used in the rest of this document...
sanitize_assignment($arg1)
Sanitize an assignment (right hand side argument of assignment).
parse_case_option()
Gets the value of the switch case.
test()
This is a function just to test the function declaration option not used in the rest of this document...
sanitize_var($arg1)
Sanitize a variable.
analyse()
Analyses the composition and updates information if required.
update_function($arg1)
Extract function arguments ($n) and updates the function accordingly.
const $C_SUCCESS
Definition patience.sh:71
const $C_ANALYSIS_ERROR
Return value on error when analysing parsed composition.
const $C_PARSE_ERROR
Return value on parse error.
const $C_WRITE_ERROR
Return value on error when writing result.
$i
Counter for the number of rounds already done.
main()
Main function.
Definition patience.sh:1382