From bc9c05218c119f50b780e3c7315f5cd9366a47b4 Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Wed, 19 Nov 2025 10:19:44 -0500 Subject: [PATCH 1/6] Improve function names --- fix_string.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/fix_string.c b/fix_string.c index 6a020c3..b64c356 100755 --- a/fix_string.c +++ b/fix_string.c @@ -180,7 +180,7 @@ static void emit_hex(int c) // Try to decide if the string is an integer: -char * isInteger(char *startP) +char * integerStringOrEmpty(char *startP) { static char temp[32]; @@ -509,7 +509,7 @@ static void emitQuotedString(char *startP) } // forward declaration -static char *emitSpecial1(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP); +static char *emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP); static char * emitSimpleVariable(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) { @@ -638,7 +638,7 @@ static char * emitString(char *startP, const char *terminatorsP, int in_quotes) if (P != startP) { burpc(&g_new, '+'); } - endP = emitSpecial1(P, in_quotes, FIX_STRING, &got); + endP = emit_enclosed_subexpr(P, in_quotes, FIX_STRING, &got); if (endP) { is_inside_quotes = FALSE; P = endP - 1; @@ -855,7 +855,7 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ for (; ; ++endP) { c = *endP; offset = g_new.m_lth; - P = emitSpecial1(endP, in_quotes, want, &got); + P = emit_enclosed_subexpr(endP, in_quotes, want, &got); if (P) { endP = P - 1; continue; @@ -906,7 +906,7 @@ emitDollarExpression(char *startP, int in_quotes, fix_typeE want, fix_typeE *got if (!isspace(c)) { is_blank = FALSE; offset = g_new.m_lth; - endP = emitSpecial1(P, in_quotes, FIX_INT, &got); + endP = emit_enclosed_subexpr(P, in_quotes, FIX_INT, &got); if (endP) { P = endP - 1; continue; @@ -960,7 +960,7 @@ emitDollarExpression(char *startP, int in_quotes, fix_typeE want, fix_typeE *got // Otherwise we will be translating Python output instead of the Bash if (g_dollar_expr_nesting_level == 1) { // Do allow array - P1 = translate_expression(g_new.m_P + start, TRUE); + P1 = translate_arithmetic_expr(g_new.m_P + start, TRUE); if (!P1) { log_return_msg("Early exit after translation"); return NULL; @@ -1018,7 +1018,7 @@ static char * emitTilde(char *startP, int in_quotes, fix_typeE want, fix_typeE * in_string = FALSE; } burpc(&g_new, '+'); - endP = emitSpecial1(P, in_quotes, FIX_STRING, &got); + endP = emit_enclosed_subexpr(P, in_quotes, FIX_STRING, &got); if (endP) { P = endP - 1; continue; @@ -1056,12 +1056,12 @@ static char * emitTilde(char *startP, int in_quotes, fix_typeE want, fix_typeE * // variable names following $ or ${, dispatches each to their appropriate handler, // and attempts a type-fixing of the result -static char * emitSpecial1(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) +static char * emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) { char *endP, *P; int c, c1, start; - log_enter("emitSpecial1 (startP=%q, in_quotes=%d, want=%t)", startP, in_quotes, want); + log_enter("emit_enclosed_subexpr (startP=%q, in_quotes=%d, want=%t)", startP, in_quotes, want); endP = NULL; start = g_new.m_lth; @@ -1142,7 +1142,7 @@ static char * emitSpecial1(char *startP, int in_quotes, fix_typeE want, fix_type return endP; } -// emitSpecial(): Just a wrapper for emitSpecial1() +// emitSpecial(): Just a wrapper for emit_enclosed_subexpr() // Returns 0 if not a special static char * emitSpecial(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) @@ -1152,7 +1152,7 @@ static char * emitSpecial(char *startP, int in_quotes, fix_typeE want, fix_typeE int start; burpc(&g_new, START_EXPAND); - endP = emitSpecial1(startP, in_quotes, want, gotP); + endP = emit_enclosed_subexpr(startP, in_quotes, want, gotP); burpc(&g_new, END_EXPAND); return endP; } @@ -1245,7 +1245,7 @@ static fix_typeE combine_types(int offset, fix_typeE want_type, fix_typeE was_ty // substitute(): Opens the door to the emit...() family of string transformations // which can call each other in all kinds of ways to fully pythonize a bash value. -// Compare to only_expand() which does the same but in a more restricted way +// Compare to expand_without_substituting() which does the same but in a more restricted way // for FIX_EXPRESSIONs. /* Convert double quotes so that all non-escaped bracketting double quotes @@ -1272,7 +1272,7 @@ static fix_typeE substitute(fix_typeE want) switch (want) { case FIX_INT: case FIX_VAR: - if (P = isInteger(g_buffer.m_P)) { + if (P = integerStringOrEmpty(g_buffer.m_P)) { burps(&g_new, P); log_return_msg("%s = simple integer, nothing to substitute", P); return FIX_INT; @@ -1415,16 +1415,16 @@ static fix_typeE substitute(fix_typeE want) } // Alternative to substitute() when we can't do any translation -// because we will later be calling translate_expression +// because we will later be calling translate_arithmetic_expr -static void only_expand(void) +static void expand_without_substituting(void) { int i, c, offset; char *P, *P1; fix_typeE got1; _BOOL is_quoted; - if (P = isInteger(g_buffer.m_P)) { + if (P = integerStringOrEmpty(g_buffer.m_P)) { burps(&g_new, P); return; } @@ -1458,7 +1458,7 @@ static void only_expand(void) /* Avoid using keywords with special values in python but not in bash */ -static void rename_special(void) +static void rename_keywords(void) { int c, c1, lth, in_word; char *P, *P1; @@ -1677,7 +1677,7 @@ static char * fix_string1(fix_typeE want, fix_typeE *gotP) } else { is_expression = TRUE; g_dollar_expr_nesting_level = 1; - only_expand(); + expand_without_substituting(); } // Everything has been written to g_new @@ -1685,7 +1685,7 @@ static char * fix_string1(fix_typeE want, fix_typeE *gotP) swap_burps(&g_buffer, &g_new); compactWhiteSpace(); - rename_special(); + rename_keywords(); finish: unmarkQuotes(is_expression); unescapeDollar(); @@ -1693,7 +1693,7 @@ static char * fix_string1(fix_typeE want, fix_typeE *gotP) char *translationP; // Don't allow array - if (translationP = translate_expression(g_buffer.m_P, FALSE)) { + if (translationP = translate_arithmetic_expr(g_buffer.m_P, FALSE)) { got = FIX_EXPRESSION; g_new.m_lth = 0; burps(&g_new, translationP); From 6f486ecf9ca9a2f5e0f2836c0865c36f56344458 Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Wed, 19 Nov 2025 10:57:34 -0500 Subject: [PATCH 2/6] Remove incorrect Glob() usages --- fix_string.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fix_string.c b/fix_string.c index b64c356..68d60d4 100755 --- a/fix_string.c +++ b/fix_string.c @@ -1290,7 +1290,8 @@ static fix_typeE substitute(fix_typeE want) case '?': case '[': // Any unprotected globbing character should trigger file globbing - if (is_outside_quotes) is_file_expansion = TRUE; + if (is_outside_quotes && !strchr("$\\", *(P-1))) + is_file_expansion = TRUE; break; case '~': case '$': From 7054f14cf5895107959deeff170b2dc0f8a1fb8a Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Wed, 19 Nov 2025 12:00:57 -0500 Subject: [PATCH 3/6] Rework integerStringOrEmpty --- fix_string.c | 122 +++++++++++++++++++++++---------------------------- 1 file changed, 55 insertions(+), 67 deletions(-) diff --git a/fix_string.c b/fix_string.c index 68d60d4..e530877 100755 --- a/fix_string.c +++ b/fix_string.c @@ -180,90 +180,78 @@ static void emit_hex(int c) // Try to decide if the string is an integer: -char * integerStringOrEmpty(char *startP) +char * integerStringOrEmpty(char *inputString) { - static char temp[32]; + static char workingString[32]; + char valid_digits[16]; - char *P, *P1, *endP; - int base, value, c; + char *readerP, *writerP, *endP; + int base, c, num_valid; - P1 = temp; - endP = temp + sizeof(temp) - 1; + memset(workingString, '\0', sizeof(workingString)); + writerP = workingString; + endP = workingString + sizeof(workingString) - 1; - // String rubbish from input - - for (P = startP; c = *P; ++P) { - switch (c) { - case START_QUOTE: - case END_QUOTE: - case '"': + // Filter out quotes and backslashes by walking reader & writer ptrs along the string + for (readerP = inputString; c = *readerP; ++readerP) { + if (c=='"' || c==START_QUOTE || c==END_QUOTE) continue; - case '\\': - if (P[1]) { - c = *++P; - } - break; - } - if (endP <= P1) { - return FALSE; + + if (c=='\\' && readerP[1]) + c = *++readerP; + + if (endP <= writerP) { + log_info("integerStringOrEmpty: input string too long"); + return NULL; } - *P1++ = c; + + *writerP++ = c; } - *P1 = '\0'; + *writerP = '\0'; + if (*workingString == '\0') { + log_info("integerStringOrEmpty: Input string is empty"); + return NULL; + } + + // Accept a leading + or - sign base = 10; - P = temp; - switch (*P) { - case '\0': - return FALSE; - case '-': - case '+': - if (!P[1]) { - return FALSE; + readerP = workingString; // N.B. reseat reader to the working string + if (strchr("+-", *readerP)) { + if (!readerP[1]) { + return NULL; } - ++P; - break; + ++readerP; } - if (*P == '0') { - switch (P[1]) { - case 'x': - case 'X': - P += 2; - if (!*P) { - return FALSE; + // Detect hex/octal indicators. Dispense with hex strings. + if (*readerP == '0') { + if (strchr("Xx", readerP[1])) { // hex + readerP += 2; + if (!*readerP) { + return NULL; } - value = read_hex(&P, sizeof(temp)); - if (*P) { - return FALSE; + read_hex(&readerP, sizeof(workingString)); + if (*readerP) { + return NULL; } - return temp; - default: + return workingString; + } + else { // octal base = 8; - break; - } } - - for (; ; ++P) { - switch (c = *P) { - case '\0': - return temp; - case '9': - case '8': - if (base < 10) { - return FALSE; - } - case '7': - case '6': - case '5': - case '4': - case '3': - case '2': - case '1': - case '0': - continue; } - return FALSE; } + + // Scan the string and return it. Any non-digit causes NULL return. + strcpy(valid_digits,"0123456789"); valid_digits[base] = '\0'; + num_valid = strspn(readerP, valid_digits); + if (num_valid != strlen(readerP)) + { +// log_info("integerStringOrEmpty: Invalid digit %c for base-%d number.", readerP[num_valid], base); + return NULL; + } + return workingString; + } /* Initialise starting g_buffer with content of stringP */ From 50e19408674f76895d087aeac65814688e8e5071 Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Sat, 22 Nov 2025 07:05:31 -0500 Subject: [PATCH 4/6] Additional comments and renames --- fix_string.c | 69 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/fix_string.c b/fix_string.c index e530877..9513a7f 100755 --- a/fix_string.c +++ b/fix_string.c @@ -178,7 +178,9 @@ static void emit_hex(int c) burpc(&g_new, c1); } -// Try to decide if the string is an integer: +// If a string denotes an integer (hex, octal, decimal strings with or without sign or quotes all okay) +// then return a pointer to a "cleaner" version of the string, else return NULL. +// If the "cleaner" string has any non-numeric content, return a NULL, so this is NOT like atoi(). char * integerStringOrEmpty(char *inputString) { @@ -279,8 +281,7 @@ static void string_to_buffer(const char *stringP) static int g_little_endian = -1; -// Convert any single quotes in g_buffer to escaped double quotes in g_new ??? -// Strictly alphanum strings should not be changed. What about $$ strings ??? +// Convert any single quotes in g_buffer to escaped double quotes, using g_new as a temp. static void replaceSingleQuotes(void) { int in_quotes; @@ -289,22 +290,30 @@ static void replaceSingleQuotes(void) in_quotes = 0; g_new.m_lth = 0; + for (P = g_buffer.m_P; c = *((unsigned char *) P); ++P) { + + // The giant switch{...} manages state when special characters are seen + // The final burpc() copies the character + switch (c) { case '"': if (!in_quotes) { - in_quotes = c; // found an opening double quote, remember it + in_quotes = c; // remember opening double quote } else if (in_quotes == c) { - in_quotes = 0; // found a terminating double quote, toss it + in_quotes = 0; // found a terminating double quote } else { - burpc(&g_new, '\\'); // found a double quote inside a non-double quote + burpc(&g_new, '\\'); // escaped, dollared or strong-quoted double quote } break; case '\'': if (!in_quotes) { - in_quotes = c; // found an opening single quote - c = '"'; - } else if (in_quotes == c || in_quotes == '$') { + in_quotes = c; // remember opening single quote + c = '"'; // but replace with double quote + } else if (in_quotes == c) { + in_quotes = 0; + c = '"'; // replace closing single with double + } else if (in_quotes == '$') { in_quotes = 0; c = '"'; } @@ -465,7 +474,7 @@ static void replaceSingleQuotes(void) swap_burps(&g_buffer, &g_new); } -// emitQuoted(): Quotes the supplied string +// emitQuoted(): Weak-quotes the supplied string into g_new static void emitQuotedString(char *startP) { char *P; @@ -478,6 +487,7 @@ static void emitQuotedString(char *startP) switch (c) { case '\0': burpc(&g_new, '"'); + log_return(); return; case '\\': burpc(&g_new, c); @@ -1130,10 +1140,12 @@ static char * emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, return endP; } -// emitSpecial(): Just a wrapper for emit_enclosed_subexpr() -// Returns 0 if not a special +// emit_delimited(): A wrapper for emit_enclosed_subexpr() +// that delimits one subexpression of a compound sequence like ${foo}"bar"$@"bu'z'z"`cat x.in` +// and is meant to be called repeatedly while traversing such a sequence. +// Returns 0 if input is not a special expression -static char * emitSpecial(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) +static char * emit_delimited(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) { char *endP; fix_typeE got; @@ -1286,7 +1298,7 @@ static fix_typeE substitute(fix_typeE want) case '`': // We have a "special" character //TODO: Very bad!!! quoted is not initialized! Compare to other invocations. - P1 = emitSpecial(P, quoted, want, &got1); + P1 = emit_delimited(P, quoted, want, &got1); if (P1 && P1 != P) { P = --P1; continue; @@ -1340,7 +1352,7 @@ static fix_typeE substitute(fix_typeE want) } quote_removal = FALSE; offset = g_new.m_lth; - P1 = emitSpecial(P, quoted, want1, &got1); + P1 = emit_delimited(P, quoted, want1, &got1); if (P1 && P1 != P) { got = combine_types(offset, want1, got, got1); P = --P1; @@ -1421,7 +1433,7 @@ static void expand_without_substituting(void) is_quoted = FALSE; for (P = g_buffer.m_P; c = *P; ++P) { offset = g_new.m_lth; - P1 = emitSpecial(P, is_quoted, FIX_EXPRESSION, &got1); + P1 = emit_delimited(P, is_quoted, FIX_EXPRESSION, &got1); if (P1 && P1 != P) { P = --P1; continue; @@ -1640,9 +1652,17 @@ static void unescapeDollar(void) " indicating we are embedding the contents of this string inside popen */ -// fix_string1(): Topmost filter function after fixBraced() has peeled off any +// fix_string1(): Second-highest filter function after fixBraced() has peeled off any // brace expressions, that is, "{1..10}" and "foo{a,bbb,cc}bar" stuff -static char * fix_string1(fix_typeE want, fix_typeE *gotP) +// N.B. The call chain is: fix_string() +// -> fixBraced() // processes braces but leaves string entire +// -> fix_string1() +// -> [[ substitute() | expand_without() ]] // expand and [[ substitute | leave alone ]] +// // and notes whether string is outer-quoted +// -> emit_enclosed_subexpr() // delimits string with START/END_EXPAND +// -> emit_delimited() // string gets stripped of its head: ${ ` ~ $(( etc +// Then the last of these plays volleyball with about six emit...() functions. +static char fix_string1(fix_typeE want, fix_typeE *gotP) { fix_typeE got; _BOOL is_expression; @@ -1660,28 +1680,27 @@ static char * fix_string1(fix_typeE want, fix_typeE *gotP) burp_close(&g_new); if (want != FIX_EXPRESSION) { + // Expand and substitute is_expression = FALSE; g_dollar_expr_nesting_level = 0; got = substitute(want); } else { + // Expand only, will translate as a numeric expression in a moment is_expression = TRUE; g_dollar_expr_nesting_level = 1; expand_without_substituting(); } - // Everything has been written to g_new - // Make it look as if it never left g_buffer - swap_burps(&g_buffer, &g_new); + swap_burps(&g_buffer, &g_new); // Move changes to the permanent buffer compactWhiteSpace(); rename_keywords(); -finish: + unmarkQuotes(is_expression); unescapeDollar(); if (is_expression) { char *translationP; - // Don't allow array if (translationP = translate_arithmetic_expr(g_buffer.m_P, FALSE)) { got = FIX_EXPRESSION; g_new.m_lth = 0; @@ -1825,7 +1844,7 @@ log_activate(); resultP = fix_string1(want, gotP); log_activate(); - log_return_msg("Brace expr not detected, pass through to emitSpecial()"); + log_return_msg("Brace expr not detected, pass through to delimitSpecialSubexpr()"); return resultP; } @@ -1833,7 +1852,7 @@ log_activate(); // Basically wraps fixBracedString(), which wraps ... char * fix_string(const char *stringP, fix_typeE want, fix_typeE *gotP) { - char *P; + char *P = NULL; if (want == FIX_NONE || !*stringP) { return (char *) stringP; From 02a6bfa1cd7d272ecf9639b2a0729cb8c62a5feb Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Thu, 27 Nov 2025 05:18:45 -0500 Subject: [PATCH 5/6] Enable substring operator ${str:x:y} Substantial improvements to names and comments Milestone checkin with some dirty code; cleanup to follow --- _touchup.sed | 2 +- fix_string.c | 400 +++++++++++++++++++++++++++++++---------------- fix_string.h | 2 +- subst.c | 8 +- translate.c | 5 - translate_expr.c | 10 +- 6 files changed, 282 insertions(+), 145 deletions(-) diff --git a/_touchup.sed b/_touchup.sed index bb40f62..f59fb31 100644 --- a/_touchup.sed +++ b/_touchup.sed @@ -1,5 +1,5 @@ #!/usr/bin/env bash sed -i -r '635 {s/_to_full_posix//;s/\)/, PATH_MAX)/}' general.c sed -i -r '295 {s/__private_//;s/__//;s/ =/; \/\//}' lib/termcap/termcap.c -sed -i -r '126,127 {s/__private_//;s/__//;}' lib/termcap/tparam.c +sed -i -r '126,127 {s/__private_//;s/__//}' lib/termcap/tparam.c diff --git a/fix_string.c b/fix_string.c index 9513a7f..350cd54 100755 --- a/fix_string.c +++ b/fix_string.c @@ -9,6 +9,8 @@ #include "burp.h" #include "fix_string.h" +extern char *skiparith __P((char *, int)); + translateT g_translate; burpT g_buffer = {0,0,0,0,0,0}; @@ -25,6 +27,9 @@ static int g_dollar_expr_nesting_level = 0; extern void seen_global(const char *nameP, _BOOL local); +static char * emitSpecialSubexpression(char *startP, fix_typeE want, fix_typeE *gotP); +static char * fixBracedString(const char *startP, fix_typeE want, fix_typeE *gotP); + char g_regmatch_var_name[] = "BASH_REMATCH"; // Finds and returns end of a quoted string (or NULL if improperly quoted) @@ -228,7 +233,7 @@ char * integerStringOrEmpty(char *inputString) // Detect hex/octal indicators. Dispense with hex strings. if (*readerP == '0') { - if (strchr("Xx", readerP[1])) { // hex + if (toupper(readerP[1]) == 'X') { // hex readerP += 2; if (!*readerP) { return NULL; @@ -509,15 +514,16 @@ static void emitQuotedString(char *startP) // forward declaration static char *emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP); -static char * emitSimpleVariable(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) +// emitSimpleVariable(): pythonifies nonbraced variables like $foo, $#, and $? +static char * emitSimpleVariable(char *afterDollarP, int in_quotes, fix_typeE want, fix_typeE *gotP) { - char *P, *endP; + char *P; int c, len; fix_typeE got; - got = FIX_NONE; + got = FIX_NONE; // Logically guaranteed to change to not-NONE - P = startP; + P = afterDollarP; switch (c = *P++) { case '*': g_translate.m_expand.m_star = TRUE; @@ -591,10 +597,11 @@ static char * emitSimpleVariable(char *startP, int in_quotes, fix_typeE want, fi break; default: if (c != '_' && !isalpha(c)) { + // Invalid character at beginning of variable name return NULL; } len = strlen(g_regmatch_var_name); - if (0 == strncmp(startP, g_regmatch_var_name, len)) { + if (0 == strncmp(afterDollarP, g_regmatch_var_name, len)) { g_regmatch_special_case = TRUE; burps(&g_new, "match_object.group"); P += len-1; @@ -678,15 +685,15 @@ static char * emitString(char *startP, const char *terminatorsP, int in_quotes) return P; } -static char * emitFunction(char *nameP, char *parm1P, char *parm2P, _BOOL indirect, int in_quotes) +static char * emitFunction(char *functionName, char *parm1P, char *parm2P, _BOOL indirect, int in_quotes) { char *endP; int offset; - log_enter("emitFunction (nameP=%q, parm1P=%q, parm2P=%q, indirect=%b, in_quotes=%d)", - nameP, parm1P, parm2P, indirect, in_quotes); + log_enter("emitFunction (functionName=%q, parm1P=%q, parm2P=%q, indirect=%b, in_quotes=%d)", + functionName, parm1P, parm2P, indirect, in_quotes); - burp(&g_new, "%s(", nameP); + burp(&g_new, "%s(", functionName); if (indirect) { g_translate.m_function.m_get_value = TRUE; burp(&g_new, "GetValue(%s.val)", parm1P); @@ -709,32 +716,56 @@ static char * emitFunction(char *nameP, char *parm1P, char *parm2P, _BOOL indire return endP; } -// Process variable names following $ or ${. Handles variables having the indirection +static char *findClosingBrace(char *buf) +{ + char *c = buf; + int brace = 0; + while (*c) + { + if (*c == '{' && *(c-1)!='\\') brace++; + else if (*c == '}' && *(c-1)!='\\') { + if(--brace == -1) { + break; + } + } + c++; + } + return c; +} + +// emitVariable(): Process variable names following $ or ${. After the closing } is seen, +// keep processing and +// Handles names having the indirection // prefix "!" and/or a postfix in the set { [*] [@] :- := :+ :? } while handing off -// simpler variable name instances to emitSimpleVariable, then finally some postprocessing. +// simpler variable name instances to emitSimpleVariable. +// Any input text that follows } is then processed with nested calls to emitters as needed. +// The input is a pointer to g_buffer[2], and the final result is written to g_new. -static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typeE want, fix_typeE *gotP) +static char * emitVariable(char *vblNameP, _BOOL is_variable_name_braced, int in_quotes, fix_typeE want, fix_typeE *gotP) { - char *P, *endP, *functionP, *end_start1P, *start2P; + char *P, *endP, *functionName, *afterVblName, *start2P; int c, is_array; _BOOL is_indirect; fix_typeE got; + int is_colon_subrange = FALSE; - log_enter("emitVariable (startP=%q, is_braced=%b, in_quotes=%d, want=%t)", - startP, is_braced, in_quotes, want); - start2P = 0; - functionP = 0; + log_enter("emitVariable (g_buffer[vblNameP]=%q, is_braced=%b, in_quotes=%d, want=%t)", + vblNameP, is_variable_name_braced, in_quotes, want); + start2P = NULL; + functionName = NULL; *gotP = FIX_STRING; - if (is_braced) { + if (is_variable_name_braced) { - is_indirect = (*startP == '!'); + is_indirect = (*vblNameP == '!'); if (is_indirect) { - ++startP; + ++vblNameP; } - for (start2P = startP; ; ++start2P) { - end_start1P = start2P; + for (start2P = vblNameP; ; ++start2P) { + afterVblName = start2P; + + // (1) Use the symbol seen to choose a helper function switch(c = *start2P) { case '\0': log_return_msg("Early return, null character"); @@ -742,14 +773,14 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ case '*': if (is_indirect == TRUE && start2P[1] == '}') { // ${!prefix*} g_translate.m_expand.m_prefixStar = TRUE; - functionP = "Expand.prefixStar"; + functionName = "Expand.prefixStar"; is_indirect = FALSE; } break; case '@': // ${!prefix@} if (is_indirect == TRUE && start2P[1] == '}') { g_translate.m_expand.m_prefixAt = TRUE; - functionP = "Expand.prefixAt"; + functionName = "Expand.prefixAt"; is_indirect = FALSE; } break; @@ -759,13 +790,13 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ if (is_indirect == TRUE) { if (!strncmp(start2P, "[*]}", 4)) { // ${!name[*]} g_translate.m_expand.m_indicesStar = TRUE; - functionP = "Expand.indicesStar"; + functionName = "Expand.indicesStar"; is_indirect = FALSE; start2P += 2; } if (!strncmp(start2P, "[@]}", 4)) { // ${!name[@]} g_translate.m_expand.m_indicesAt = TRUE; - functionP = "Expand.indicesAt"; + functionName = "Expand.indicesAt"; is_indirect = FALSE; start2P += 2; } @@ -774,61 +805,156 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ break; case '-': g_translate.m_expand.m_minus = TRUE; - functionP = "Expand.minus"; + functionName = "Expand.minus"; break; case '=': g_translate.m_expand.m_eq = TRUE; - functionP = "Expand.eq"; + functionName = "Expand.eq"; break; case '?': g_translate.m_expand.m_qmark = TRUE; - functionP = "Expand.qmark"; + functionName = "Expand.qmark"; break; case '+': g_translate.m_expand.m_plus = TRUE; - functionP = "Expand.plus"; + functionName = "Expand.plus"; break; case ':': switch (*++start2P) { case '-': g_translate.m_expand.m_colon_minus = TRUE; - functionP = "Expand.colonMinus"; + functionName = "Expand.colonMinus"; break; case '=': g_translate.m_expand.m_colon_eq = TRUE; - functionP = "Expand.colonEq"; + functionName = "Expand.colonEq"; break; case '?': g_translate.m_expand.m_colon_qmark = TRUE; - functionP = "Expand.colonQmark"; + functionName = "Expand.colonQmark"; break; case '+': g_translate.m_expand.m_colon_plus = TRUE; - functionP = "Expand.colonPlus"; + functionName = "Expand.colonPlus"; break; default: + // The character after : is none of the above. + // Assume it's a subrange expr like ${x:y:z} + is_colon_subrange = TRUE; --start2P; } break; default: continue; } - if (!functionP) { + + // (2) Indirect references and string operations do not use functions + if (!functionName) { if (is_indirect) { - c = *end_start1P; - *end_start1P = '\0'; - burp(&g_new,"GetValue(%s.val)", startP); - *end_start1P = c; - endP = end_start1P; + c = *afterVblName; + *afterVblName = '\0'; + burp(&g_new,"GetValue(%s.val)", vblNameP); + *afterVblName = c; + endP = afterVblName; goto done; } - } else { + else if (is_colon_subrange) { + char *tailP = start2P; + is_colon_subrange = FALSE; + +{ + //TODO Any extra considerations for array-type variables? + char *parameter1P, *parameter2P, *ogParameter1P; + char *intP1, *intP2; + int int1, int2; + char tmp[32]; + burpT g_scratch0 = {0,0,0,0,0,0}; + burpT g_scratch = {0,0,0,0,0,0}; + burpT bufferBackup = {0,0,0,0,0,0}; + + // Delimit the first parameter + parameter1P = start2P+1; // go past ':' to the first parameter + if (*parameter1P == ':') { // check for the form ${var::y} + // Transform into ${var:0:y} + burp_insert(&g_buffer, parameter1P-g_buffer.m_P, "0"); + } + + // Delimit the second parameter + parameter2P = skiparith(parameter1P, ':'); // go to next ':' (or the end) + if (*parameter2P == '\0') // check for the form ${var:x} -- parameter2 is empty string + { + // Reseat both parameters in scratch buffers + endP = strrchr(parameter1P, '}'); + assert(endP); + burp(&g_scratch0, "%.*s", (int)(endP-parameter1P), parameter1P); +// endP++; // The "emit...() volleyball algo" requires us to return a pointer no further than the last } +//changes nothing. endP points to brace: endP = g_buffer.m_P+(int)(endP-vblNameP+2); + burps(&g_scratch, parameter2P); + parameter1P = g_scratch0.m_P; + parameter2P = g_scratch.m_P; + } + else if (*parameter2P == ':') { // check for forms ${var:x:y} and ${var:x:} + // As above, reseat both parameters in scratch buffers + parameter2P++; + endP = findClosingBrace(parameter2P); // forward search for closing '}' + assert(endP); + if (parameter2P == endP) { // the form ${var:x:} + burp_insert(&g_buffer, (int)(parameter2P-g_buffer.m_P), "0"); + endP++; + } + burp(&g_scratch0, "%.*s", (int)(parameter2P-parameter1P)-1, parameter1P); + burp(&g_scratch, "%.*s", (int)(endP-parameter2P), parameter2P); +// endP++; +//changes nothing. endP points to brace: endP = g_buffer.m_P+(int)(endP-vblNameP+2); + parameter1P = g_scratch0.m_P; + parameter2P = g_scratch.m_P; + } + else goto done; // unexpected behavior from skiparith(). Malformed input? + + intP1 = parameter1P ? integerStringOrEmpty(parameter1P) : NULL; + if (intP1) int1 = atoi(intP1); + intP2 = parameter2P ? integerStringOrEmpty(parameter2P) : NULL; + if (intP2) int2 = atoi(intP2); + + // Back up the original buffer & reseat its internal pointers + burp(&bufferBackup, g_buffer.m_P); + vblNameP = bufferBackup.m_P + (vblNameP-g_buffer.m_P); +// endP = bufferBackup.m_P + (endP-g_buffer.m_P); + sprintf(tmp, ":%s", parameter1P); + ogParameter1P = strstr(bufferBackup.m_P, tmp) + 1; + + /**** Construct the pythonic-style range expression ****/ + + // (1) Set up the preamble +// burp_reset(&g_buffer); + burp(&g_new, "%.*s[", (ogParameter1P-vblNameP)-1, vblNameP); + + // (2) Set up the range, niceifying when there are numeric literals + if (intP1 && intP2) { + burp(&g_new, "%d:%d]", int1, (int2>0) ? int1+int2 : int2); + } + else if (intP1) { // 7: 7:x+2 + burp(&g_new, "%d:%s]", int1, (parameter2P ? parameter2P : "")); + } + else if (intP2) { + burp(&g_new, "%s:%d]", (parameter1P ? parameter1P : ""), int2); + } + else { // Neither parameter is a numeric literal + burp(&g_new, "%s:%s+%s]", parameter1P, parameter1P, parameter2P); + } + +// burps(&g_new, g_buffer.m_P); + goto done; +} + } // is_colon_subrange + } // no helper function + else { // helper function assert(start2P); ++start2P; - c = *end_start1P; - *end_start1P = '\0'; - endP = emitFunction(functionP, startP, start2P, is_indirect, in_quotes); - *end_start1P = c; + c = *afterVblName; + *afterVblName = '\0'; + endP = emitFunction(functionName, vblNameP, start2P, is_indirect, in_quotes); + *afterVblName = c; if (!endP) { log_return_msg("Early return, null character"); return NULL; @@ -836,13 +962,17 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ goto done; } break; - } } - endP = emitSimpleVariable(startP, in_quotes, want, gotP); + } + } + + // N.B. Because of gotos in the code above, this only runs for unbraced variables like $foo: + endP = emitSimpleVariable(vblNameP, in_quotes, want, gotP); + done: - if (is_braced) { + if (is_variable_name_braced) { int offset; - is_array = (*endP == '[' || g_regmatch_special_case); + is_array = (*endP == '[' || g_regmatch_special_case); //TODO Here, endP = after_vbl_name if (is_array) { *gotP = FIX_VAR; if (g_regmatch_special_case) { @@ -850,6 +980,8 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ *endP = '('; } } + + // Attend to everything up to the closing '}' for (; ; ++endP) { c = *endP; offset = g_new.m_lth; @@ -858,26 +990,26 @@ static char * emitVariable(char *startP, _BOOL is_braced, int in_quotes, fix_typ endP = P - 1; continue; } - // Undo emission - g_new.m_lth = offset; + g_new.m_lth = offset; // Undo emission switch (c) { - case '\0': - log_return_msg("Early return, null character"); - return NULL; - case '}': - ++endP; - goto finish; - case ']': - if (is_array) { - // We don't know what an array is of.. - burps(&g_new, g_regmatch_special_case ? ")" : "]"); - is_array = FALSE; - g_regmatch_special_case = FALSE; - continue; + case '\0': + log_return_msg("Early return, null character"); + return NULL; + case '}': + ++endP; + goto finish; // the single "right" way to end this loop + case ']': + if (is_array) { + // We don't know what an array is of.. + burps(&g_new, g_regmatch_special_case ? ")" : "]"); + is_array = FALSE; + g_regmatch_special_case = FALSE; + continue; + } } - } burpc(&g_new, c); - } } + } + } finish: log_return(); return endP; @@ -1024,15 +1156,16 @@ static char * emitTilde(char *startP, int in_quotes, fix_typeE want, fix_typeE * // Undo emission g_new.m_lth = offset; switch (c = *P) { - case '_': - case '-': - case '.': - case '/': - break; - default: - if (!isalnum(c)) { - goto done; - } } + case '_': + case '-': + case '.': + case '/': + break; + default: + if (!isalnum(c)) { + goto done; + } + } if (!in_string) { burps(&g_new, "+\""); in_string = TRUE; @@ -1051,8 +1184,8 @@ static char * emitTilde(char *startP, int in_quotes, fix_typeE want, fix_typeE * } // Identifies tilde, backtick, subshell and double-paren expressions as well as -// variable names following $ or ${, dispatches each to their appropriate handler, -// and attempts a type-fixing of the result +// braced & unbraced variable exprs ${x} or $x , dispatches each to their appropriate handler, +// and attempts a type-fixing of the result. The results are written to g_new. static char * emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) { @@ -1143,7 +1276,9 @@ static char * emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, // emit_delimited(): A wrapper for emit_enclosed_subexpr() // that delimits one subexpression of a compound sequence like ${foo}"bar"$@"bu'z'z"`cat x.in` // and is meant to be called repeatedly while traversing such a sequence. -// Returns 0 if input is not a special expression +// Writes the result to g_new and returns NULL if the input is not a special expression. +// Opens the door to the emit...() family of string transformations +// which can call each other in all kinds of ways to fully pythonize a bash value. static char * emit_delimited(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP) { @@ -1157,6 +1292,7 @@ static char * emit_delimited(char *startP, int in_quotes, fix_typeE want, fix_ty return endP; } +// combine_types(): Applies type modifier to a value in g_new to make it compatible with its predecessor static fix_typeE combine_types(int offset, fix_typeE want_type, fix_typeE was_type, fix_typeE new_type) { char *P; @@ -1207,7 +1343,7 @@ static fix_typeE combine_types(int offset, fix_typeE want_type, fix_typeE was_ty if (was_type == FIX_NONE) { // Do nothing more - log_return_msg("Simple cast"); + log_return_msg("Untyped value, will not cast"); return new_type; } if (was_type == new_type) { @@ -1243,18 +1379,14 @@ static fix_typeE combine_types(int offset, fix_typeE want_type, fix_typeE was_ty return FIX_STRING; } -// substitute(): Opens the door to the emit...() family of string transformations -// which can call each other in all kinds of ways to fully pythonize a bash value. -// Compare to expand_without_substituting() which does the same but in a more restricted way -// for FIX_EXPRESSIONs. +// substitute(): Traverses g_buffer (mostly invisibly), performing expansion & substitution +// on each subexpression we see along the way and writing the result to g_new. Compare to +// emit_without_substituting(), which only does the former and is intended for FIX_EXPRESSIONs. +// Returns the type of the result (FIX_INT, etc.) /* Convert double quotes so that all non-escaped bracketting double quotes * are given clear internal codes to simplify subsequent logic * Recursively expand expressions as needed - * - * We can assume we are working with something considered a bash word - * - * Returns the type of the result */ static fix_typeE substitute(fix_typeE want) @@ -1262,7 +1394,8 @@ static fix_typeE substitute(fix_typeE want) fix_typeE got; // What I had fix_typeE got1; // What I'm now seeing fix_typeE want1; - int i, in_quotes, quoted, c, c1, c2, offset; + int i, startquote_offset, quoted, c, c1, c2, offset; + const int NOT_QUOTED = -1; char **arrayPP, *P, *P1, *P2; _BOOL is_file_expansion, quote_removal; _BOOL is_outside_quotes; @@ -1281,31 +1414,29 @@ static fix_typeE substitute(fix_typeE want) is_outside_quotes = TRUE; is_file_expansion = FALSE; - offset = g_new.m_lth; + offset = 0; for (P = g_buffer.m_P; c = *P ; ++P) { - switch (c) { case '*': case '?': case '[': - // Any unprotected globbing character should trigger file globbing - if (is_outside_quotes && !strchr("$\\", *(P-1))) + // Any unprotected globbing character should trigger globbing + if (is_outside_quotes && !(*(P-1)=='$' || *(P-1)=='\\')) is_file_expansion = TRUE; break; case '~': case '$': case '`': - // We have a "special" character - //TODO: Very bad!!! quoted is not initialized! Compare to other invocations. + // We have a special subexpression. Put an expanded & delimited version in g_new P1 = emit_delimited(P, quoted, want, &got1); + // Jump past the subexpression if (P1 && P1 != P) { P = --P1; continue; } // Restore to where we were - g_new.m_lth = offset; - g_new.m_P[offset] = '\0'; + burp_reset(&g_new); //TODO This wipes out the nice new g_buffer we just built in emit_delimited() break; case '"': is_outside_quotes = !is_outside_quotes; @@ -1314,37 +1445,41 @@ static fix_typeE substitute(fix_typeE want) // Skip/protect escaped characters if (P[1]) { ++P; - } } } - g_new.m_lth = offset; + } + } + } +// g_new.m_lth = offset; //TODO This wipes out the g_new we just built... for no reason? - got = FIX_NONE; - in_quotes = -1; + got = FIX_NONE; //TODO Try initting to STRING. It looks like NONE should NEVER be used + startquote_offset = NOT_QUOTED; quote_removal = FALSE; want1 = (is_file_expansion ? FIX_STRING : want); - for (P = g_buffer.m_P; ; ++P) { + for (P = g_buffer.m_P; ; ++P) { //TODO Having expanded g_buffer into g_new above, shouldn't we stick to g_new here? c = *P; if (c == '"') { - // Quote removal + // Note the quote mark and discard it. quote_removal = TRUE; continue; } - switch (c) { + switch (c) { //TODO: Wait a minute. Shouldnt these chars have been removed from g_new by the first loop? + //TODO: This is ridiculous. What do the earlier versions of bash2py do? case '\0': case '~': case '$': case '`': - // We have a "special" character or the null terminator. - if (in_quotes < 0) { - quoted = quote_removal; + // We have a "special" character or the null terminator. Reset quoting & combine types (looking back to startquote). + // Substitute the substring. Combine types (right here at m_lth). + if (startquote_offset == NOT_QUOTED) { + quoted = quote_removal; // TODO This eats my '$' char so combine_types was not being run, but catches my '\0' } else { - burpc(&g_new, END_QUOTE); + burpc(&g_new, END_QUOTE); // N.B. This is the counterpart to START_QUOTE 25 lines below got1 = FIX_STRING; - got = combine_types(in_quotes, want1, got, got1); - in_quotes = -1; + got = combine_types(startquote_offset, want1, got, got1); // (offset, want_type, was_type, new_type); + startquote_offset = NOT_QUOTED; quoted = 1; } if (!c) { @@ -1356,7 +1491,7 @@ static fix_typeE substitute(fix_typeE want) if (P1 && P1 != P) { got = combine_types(offset, want1, got, got1); P = --P1; - continue; + continue; // N.B. continue changes the flow. } // Restore to where we were g_new.m_lth = offset; @@ -1365,9 +1500,9 @@ static fix_typeE substitute(fix_typeE want) quote_removal = FALSE; - // Saw some normal character - if (in_quotes < 0) { - in_quotes = g_new.m_lth; + // Saw some normal character //TODO: Best if the new subsequence registers as a FIX_STRING? + if (startquote_offset == NOT_QUOTED) { + startquote_offset = g_new.m_lth; burpc(&g_new, START_QUOTE); } if (c == '\\') { @@ -1560,7 +1695,7 @@ static void rename_keywords(void) } } - +// compactWhiteSpace(): Merge consecutive spaces in g_buffer into one static void compactWhiteSpace(void) { char *P, *P1, *P3; @@ -1580,6 +1715,8 @@ static void compactWhiteSpace(void) } } +// unmarkQuotes(): Remove {START/END}_QUOTE markers (not quote characters) +// from g_buffer. (These markers are sometimes added by substitute().) static void unmarkQuotes(_BOOL delete_quotes) { char *P, *P1; @@ -1611,6 +1748,8 @@ static void unmarkQuotes(_BOOL delete_quotes) g_buffer.m_lth = P1 - g_buffer.m_P; } +// unmarkExpand(): Remove the {START/END}_EXPAND markers from g_buffer +// which were inserted by emit_delimited() static void unmarkExpand(void) { char *P, *P1; @@ -1646,23 +1785,18 @@ static void unescapeDollar(void) return; } -/* Transforms g_buffer - * Separator can be: - + indicating concatonate parts (normal behaviour) - " indicating we are embedding the contents of this string inside popen - */ -// fix_string1(): Second-highest filter function after fixBraced() has peeled off any -// brace expressions, that is, "{1..10}" and "foo{a,bbb,cc}bar" stuff +// fix_string1(): Second-highest transformation filter after fixBraced() +// Transforms g_buffer and returns a pointer to it // N.B. The call chain is: fix_string() -// -> fixBraced() // processes braces but leaves string entire +// -> fixBraced() // processes "{1..10}" and "foo{a,bbb,bb}bar" constructs only // -> fix_string1() // -> [[ substitute() | expand_without() ]] // expand and [[ substitute | leave alone ]] -// // and notes whether string is outer-quoted -// -> emit_enclosed_subexpr() // delimits string with START/END_EXPAND -// -> emit_delimited() // string gets stripped of its head: ${ ` ~ $(( etc -// Then the last of these plays volleyball with about six emit...() functions. -static char fix_string1(fix_typeE want, fix_typeE *gotP) +// // and note whether string is outer-quoted +// -> emit_enclosed_subexpr() // string gets stripped of its head: ${ ` ~ $(( etc +// -> emit_delimited() // delimits string with START/END_EXPAND +// Then the last of these plays volleyball with the emit...() functions. +static char * fix_string1(fix_typeE want, fix_typeE *gotP) { fix_typeE got; _BOOL is_expression; @@ -1691,7 +1825,7 @@ static char fix_string1(fix_typeE want, fix_typeE *gotP) expand_without_substituting(); } - swap_burps(&g_buffer, &g_new); // Move changes to the permanent buffer + swap_burps(&g_buffer, &g_new); // Move changes ("new") to the permanent buffer compactWhiteSpace(); rename_keywords(); @@ -1722,7 +1856,7 @@ static char fix_string1(fix_typeE want, fix_typeE *gotP) // fixBracedString(): The topmost filtering function under fix_string(). // Processes brace expressions like {1..10} and foo{a,bbb,cc}bar but NOT // dollar-brace expressions like ${x}. Notice how the code looks for '.' or ',' inside {}. - +// Can return a pointer to g_buffer or g_braced static char * fixBracedString(const char *startP, fix_typeE want, fix_typeE *gotP) { extern char **brace_expand(char *textP); // from braces.c in the bash API @@ -1802,6 +1936,7 @@ log_deactivate(); resultP = NULL; arrayPP = brace_expand((char *) startP); // calling bash API here if (arrayPP) { + // Transform array into a pythonic list: [x, y, z] if (arrayPP[0]) { fix_typeE single_want_type; int i; @@ -1836,8 +1971,9 @@ log_deactivate(); if (resultP) { log_activate(); log_return_msg("brace expression processed"); - return resultP; - } } + return resultP; // pointer to g_braced + } + } // close array block pass_through: string_to_buffer(startP); diff --git a/fix_string.h b/fix_string.h index 21888b8..96ba21b 100755 --- a/fix_string.h +++ b/fix_string.h @@ -91,7 +91,7 @@ char *endQuotedString(char *startP); char *endArray(char *startP); char *endExpand(char *startP); -char *translate_expression(char *startP, _BOOL allow_array); +char *translate_arithmetic_expr(char *startP, _BOOL allow_array); char *fix_string(const char *stringP, fix_typeE type, fix_typeE *gotP); diff --git a/subst.c b/subst.c index f0f3ffe..f4b7e0e 100644 --- a/subst.c +++ b/subst.c @@ -289,7 +289,10 @@ static void parameter_brace_expand_error __P((char *, char *)); static int valid_length_expression __P((char *)); static intmax_t parameter_brace_expand_length __P((char *)); +#ifndef BASH2PY static char *skiparith __P((char *, int)); +#endif + static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *)); static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **)); static char *mb_substring __P((char *, int, int)); @@ -6197,7 +6200,10 @@ parameter_brace_expand_length (name) 2. If the substring contains a `?', read past one `:' for each `?'. */ -static char * +#ifndef BASH2PY +static +#endif +char * skiparith (substr, delim) char *substr; int delim; diff --git a/translate.c b/translate.c index eabe6cd..5d07080 100755 --- a/translate.c +++ b/translate.c @@ -818,11 +818,6 @@ static void indent (int amount) } } -typedef struct commentS { - struct commentS *m_nextP; - int m_byte; - char *m_textP; -} commentT; static void translate_unary_operation(char *operatorP, int complex1, char *term1P) { diff --git a/translate_expr.c b/translate_expr.c index 30c22d1..a6f665c 100755 --- a/translate_expr.c +++ b/translate_expr.c @@ -1177,13 +1177,13 @@ print_translation(nodeT *nodeP) */ -char *translate_expression(char *stringP, _BOOL allow_array) +char *translate_arithmetic_expr(char *stringP, _BOOL allow_array) { nodeT *treeP; int ret; -log_activate_in("translate_expression"); - log_enter("translate_expression (stringP=%s, allow_array=%b)", stringP, allow_array); +log_activate_in("translate_arithmetic_expr"); + log_enter("translate_arithmetic_expr (stringP=%s, allow_array=%b)", stringP, allow_array); g_stringP = g_atP = stringP; skipspace(0); @@ -1205,7 +1205,7 @@ log_activate_in("translate_expression"); } log_return_msg("g_expression = %q", g_expression.m_P); -log_deactivate_in("translate_expression"); +log_deactivate_in("translate_arithmetic_expr"); return g_expression.m_P; } @@ -1226,7 +1226,7 @@ main(int argc, char **argv) bufferP[lth-1] = '\0'; } printf("> %s\n", bufferP); - translationP = translate_expression(bufferP, TRUE); + translationP = translate_arithmetic_expr(bufferP, TRUE); if (!translationP) { printf("Can't translate\n"); continue; From 0728b962672c9cffa207be9fbba8e2b33c52b47f Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Thu, 27 Nov 2025 10:10:24 -0500 Subject: [PATCH 6/6] Translate missing substring indices like ${str::3} Debug and cleanup substitute() Indentation and comment improvements --- fix_string.c | 235 +++++++++++++++++++++++++-------------------------- 1 file changed, 115 insertions(+), 120 deletions(-) diff --git a/fix_string.c b/fix_string.c index 350cd54..9fcbea0 100755 --- a/fix_string.c +++ b/fix_string.c @@ -514,7 +514,7 @@ static void emitQuotedString(char *startP) // forward declaration static char *emit_enclosed_subexpr(char *startP, int in_quotes, fix_typeE want, fix_typeE *gotP); -// emitSimpleVariable(): pythonifies nonbraced variables like $foo, $#, and $? +// emitSimpleVariable(): pythonifies nonbraced variables like $foo, $#, and $?, writing to g_new static char * emitSimpleVariable(char *afterDollarP, int in_quotes, fix_typeE want, fix_typeE *gotP) { char *P; @@ -734,10 +734,10 @@ static char *findClosingBrace(char *buf) } // emitVariable(): Process variable names following $ or ${. After the closing } is seen, -// keep processing and -// Handles names having the indirection -// prefix "!" and/or a postfix in the set { [*] [@] :- := :+ :? } while handing off -// simpler variable name instances to emitSimpleVariable. +// scan forward and recurse into emit_enclosed() to handle further special subexpressions. +// This function handles names having the indirection prefix "!" and/or a postfix in the set +// [*] [@] :- := :+ :? or one of the string operators : / % # +// while handing off simpler variable name instances to emitSimpleVariable. // Any input text that follows } is then processed with nested calls to emitters as needed. // The input is a pointer to g_buffer[2], and the final result is written to g_new. @@ -749,7 +749,7 @@ static char * emitVariable(char *vblNameP, _BOOL is_variable_name_braced, int in fix_typeE got; int is_colon_subrange = FALSE; - log_enter("emitVariable (g_buffer[vblNameP]=%q, is_braced=%b, in_quotes=%d, want=%t)", + log_enter("emitVariable (g_buffer[vblName]=%q, is_braced=%b, in_quotes=%d, want=%t)", vblNameP, is_variable_name_braced, in_quotes, want); start2P = NULL; functionName = NULL; @@ -854,98 +854,97 @@ static char * emitVariable(char *vblNameP, _BOOL is_variable_name_braced, int in c = *afterVblName; *afterVblName = '\0'; burp(&g_new,"GetValue(%s.val)", vblNameP); - *afterVblName = c; + *afterVblName = c; endP = afterVblName; goto done; } else if (is_colon_subrange) { char *tailP = start2P; is_colon_subrange = FALSE; - -{ - //TODO Any extra considerations for array-type variables? - char *parameter1P, *parameter2P, *ogParameter1P; - char *intP1, *intP2; - int int1, int2; - char tmp[32]; - burpT g_scratch0 = {0,0,0,0,0,0}; - burpT g_scratch = {0,0,0,0,0,0}; - burpT bufferBackup = {0,0,0,0,0,0}; - - // Delimit the first parameter - parameter1P = start2P+1; // go past ':' to the first parameter - if (*parameter1P == ':') { // check for the form ${var::y} - // Transform into ${var:0:y} - burp_insert(&g_buffer, parameter1P-g_buffer.m_P, "0"); - } - - // Delimit the second parameter - parameter2P = skiparith(parameter1P, ':'); // go to next ':' (or the end) - if (*parameter2P == '\0') // check for the form ${var:x} -- parameter2 is empty string - { - // Reseat both parameters in scratch buffers - endP = strrchr(parameter1P, '}'); - assert(endP); - burp(&g_scratch0, "%.*s", (int)(endP-parameter1P), parameter1P); -// endP++; // The "emit...() volleyball algo" requires us to return a pointer no further than the last } -//changes nothing. endP points to brace: endP = g_buffer.m_P+(int)(endP-vblNameP+2); - burps(&g_scratch, parameter2P); - parameter1P = g_scratch0.m_P; - parameter2P = g_scratch.m_P; - } - else if (*parameter2P == ':') { // check for forms ${var:x:y} and ${var:x:} - // As above, reseat both parameters in scratch buffers - parameter2P++; - endP = findClosingBrace(parameter2P); // forward search for closing '}' - assert(endP); - if (parameter2P == endP) { // the form ${var:x:} - burp_insert(&g_buffer, (int)(parameter2P-g_buffer.m_P), "0"); - endP++; - } - burp(&g_scratch0, "%.*s", (int)(parameter2P-parameter1P)-1, parameter1P); - burp(&g_scratch, "%.*s", (int)(endP-parameter2P), parameter2P); -// endP++; -//changes nothing. endP points to brace: endP = g_buffer.m_P+(int)(endP-vblNameP+2); - parameter1P = g_scratch0.m_P; - parameter2P = g_scratch.m_P; - } - else goto done; // unexpected behavior from skiparith(). Malformed input? - - intP1 = parameter1P ? integerStringOrEmpty(parameter1P) : NULL; - if (intP1) int1 = atoi(intP1); - intP2 = parameter2P ? integerStringOrEmpty(parameter2P) : NULL; - if (intP2) int2 = atoi(intP2); - - // Back up the original buffer & reseat its internal pointers - burp(&bufferBackup, g_buffer.m_P); - vblNameP = bufferBackup.m_P + (vblNameP-g_buffer.m_P); -// endP = bufferBackup.m_P + (endP-g_buffer.m_P); - sprintf(tmp, ":%s", parameter1P); - ogParameter1P = strstr(bufferBackup.m_P, tmp) + 1; - - /**** Construct the pythonic-style range expression ****/ - - // (1) Set up the preamble -// burp_reset(&g_buffer); - burp(&g_new, "%.*s[", (ogParameter1P-vblNameP)-1, vblNameP); - - // (2) Set up the range, niceifying when there are numeric literals - if (intP1 && intP2) { - burp(&g_new, "%d:%d]", int1, (int2>0) ? int1+int2 : int2); - } - else if (intP1) { // 7: 7:x+2 - burp(&g_new, "%d:%s]", int1, (parameter2P ? parameter2P : "")); - } - else if (intP2) { - burp(&g_new, "%s:%d]", (parameter1P ? parameter1P : ""), int2); - } - else { // Neither parameter is a numeric literal - burp(&g_new, "%s:%s+%s]", parameter1P, parameter1P, parameter2P); - } - -// burps(&g_new, g_buffer.m_P); - goto done; -} + { + //TODO Any extra considerations for array-type variables? + char *parameter1P, *parameter2P, *ogParameter1P; + char *intP1, *intP2; + int int1, int2; + char tmp[32]; + burpT g_scratch0 = {0,0,0,0,0,0}; + burpT g_scratch = {0,0,0,0,0,0}; + burpT bufferBackup = {0,0,0,0,0,0}; + + // Delimit the first parameter + parameter1P = start2P+1; // go past ':' to the first parameter + //if (*parameter1P == ' ') parameter1P++; + if (*parameter1P == ':') { // check for the form ${var::y} + // Transform into ${var:0:y} + burp_insert(&g_buffer, parameter1P-g_buffer.m_P, "0"); + } + + // Delimit the second parameter + parameter2P = skiparith(parameter1P, ':'); // go to next ':' (or the end) + if (*parameter2P == '\0') // check for the form ${var:x} -- parameter2 is empty string + { + // Reseat both parameters in scratch buffers + endP = strrchr(parameter1P, '}'); + assert(endP); + burp(&g_scratch0, "%.*s", (int)(endP-parameter1P), parameter1P); + burps(&g_scratch, parameter2P); + parameter1P = g_scratch0.m_P; + parameter2P = g_scratch.m_P; + } + else if (*parameter2P == ':') { // check for forms ${var:x:y} and ${var:x:} + // As above, reseat both parameters in scratch buffers + parameter2P++; + endP = findClosingBrace(parameter2P); // forward search for closing '}' + assert(endP); + if (parameter2P == endP) { // the form ${var:x:} + burp_insert(&g_buffer, (int)(parameter2P-g_buffer.m_P), "0"); + endP++; + } + burp(&g_scratch0, "%.*s", (int)(parameter2P-parameter1P)-1, parameter1P); + burp(&g_scratch, "%.*s", (int)(endP-parameter2P), parameter2P); + parameter1P = g_scratch0.m_P; + parameter2P = g_scratch.m_P; + } + else goto done; // unexpected behavior from skiparith(). Malformed input? + + intP1 = parameter1P ? integerStringOrEmpty(parameter1P) : NULL; + if (intP1) int1 = atoi(intP1); + intP2 = parameter2P ? integerStringOrEmpty(parameter2P) : NULL; + if (intP2) int2 = atoi(intP2); + + // Back up the original buffer & reseat its internal pointers + burp(&bufferBackup, g_buffer.m_P); + vblNameP = bufferBackup.m_P + (vblNameP-g_buffer.m_P); + sprintf(tmp, ":%s", parameter1P); + ogParameter1P = strstr(bufferBackup.m_P, tmp) + 1; + + /**** Construct the pythonic-style range expression ****/ + + // (1) Set up the preamble + burp(&g_new, "%.*s[", (ogParameter1P-vblNameP)-1, vblNameP); + + // (2) Set up the range, niceifying when there are numeric literals + if (intP1 && intP2) { + burp(&g_new, "%d:%d]", int1, (int2>0) ? int1+int2 : int2); + } + else if (intP1) { // 7: 7:x+2 + burp(&g_new, "%d:%s]", int1, (parameter2P ? parameter2P : "")); + } + else if (intP2) { + burp(&g_new, "%s:%d]", (parameter1P ? parameter1P : ""), int2); + } + else { // Neither parameter is a numeric literal + if (!parameter1P) { + burp(&g_new, ":%s]", parameter2P); + } else if (!parameter2P) { + burp(&g_new, "%s:]", parameter1P); + } else { + burp(&g_new, "%s:%s+%s]", parameter1P, parameter1P, parameter2P); + } + } + + goto done; + } } // is_colon_subrange } // no helper function else { // helper function @@ -972,7 +971,7 @@ static char * emitVariable(char *vblNameP, _BOOL is_variable_name_braced, int in if (is_variable_name_braced) { int offset; - is_array = (*endP == '[' || g_regmatch_special_case); //TODO Here, endP = after_vbl_name + is_array = (*endP == '[' || g_regmatch_special_case); if (is_array) { *gotP = FIX_VAR; if (g_regmatch_special_case) { @@ -1425,19 +1424,6 @@ static fix_typeE substitute(fix_typeE want) if (is_outside_quotes && !(*(P-1)=='$' || *(P-1)=='\\')) is_file_expansion = TRUE; break; - case '~': - case '$': - case '`': - // We have a special subexpression. Put an expanded & delimited version in g_new - P1 = emit_delimited(P, quoted, want, &got1); - // Jump past the subexpression - if (P1 && P1 != P) { - P = --P1; - continue; - } - // Restore to where we were - burp_reset(&g_new); //TODO This wipes out the nice new g_buffer we just built in emit_delimited() - break; case '"': is_outside_quotes = !is_outside_quotes; break; @@ -1448,14 +1434,20 @@ static fix_typeE substitute(fix_typeE want) } } } -// g_new.m_lth = offset; //TODO This wipes out the g_new we just built... for no reason? got = FIX_NONE; //TODO Try initting to STRING. It looks like NONE should NEVER be used startquote_offset = NOT_QUOTED; quote_removal = FALSE; want1 = (is_file_expansion ? FIX_STRING : want); - for (P = g_buffer.m_P; ; ++P) { //TODO Having expanded g_buffer into g_new above, shouldn't we stick to g_new here? + // The strange treatment of quotes in the following code is meant to trade bash quoting for python quoting. + // Since for example, echo ${abc}de"fg"hi would translate to print(str(abc)+"defghi") + // the strategy is to strip the quotes from literal text in the input, determine where python + // needs them, and insert new ones there. To keep track of this we use the markers {START/END}_QUOTE. + // Meanwhile we cannot just toss the quotes from the special bash variable "$*". + // That's what 'quoted' is used to keep track of as you can see in emitSimpleVariable().. + + for (P = g_buffer.m_P; ; ++P) { c = *P; @@ -1465,24 +1457,25 @@ static fix_typeE substitute(fix_typeE want) continue; } - switch (c) { //TODO: Wait a minute. Shouldnt these chars have been removed from g_new by the first loop? - //TODO: This is ridiculous. What do the earlier versions of bash2py do? + switch (c) { case '\0': case '~': case '$': case '`': - // We have a "special" character or the null terminator. Reset quoting & combine types (looking back to startquote). - // Substitute the substring. Combine types (right here at m_lth). + // We have a "special" character or the null terminator. if (startquote_offset == NOT_QUOTED) { - quoted = quote_removal; // TODO This eats my '$' char so combine_types was not being run, but catches my '\0' + quoted = quote_removal; } else { - burpc(&g_new, END_QUOTE); // N.B. This is the counterpart to START_QUOTE 25 lines below + // We are in a START_QUOTE-markedup section and have reached its end. Close it with END_QUOTE. + burpc(&g_new, END_QUOTE); + + // Combine the type of the QUOTE-markup section with the type of whatever precedes it got1 = FIX_STRING; - got = combine_types(startquote_offset, want1, got, got1); // (offset, want_type, was_type, new_type); + got = combine_types(startquote_offset, want1, got, got1); startquote_offset = NOT_QUOTED; quoted = 1; } - if (!c) { + if (!c) { // '\0' means we are done processing goto done; } quote_removal = FALSE; @@ -1491,17 +1484,18 @@ static fix_typeE substitute(fix_typeE want) if (P1 && P1 != P) { got = combine_types(offset, want1, got, got1); P = --P1; - continue; // N.B. continue changes the flow. + continue; // N.B. this changes the control flow. } - // Restore to where we were + // Restore to where we were. //TODO is this always correct even if emit_delim() changews the buffer length? g_new.m_lth = offset; g_new.m_P[offset] = '\0'; } quote_removal = FALSE; - // Saw some normal character //TODO: Best if the new subsequence registers as a FIX_STRING? + // Saw some normal character if (startquote_offset == NOT_QUOTED) { + // Set the START marker for a new subsection of text that will need quoting startquote_offset = g_new.m_lth; burpc(&g_new, START_QUOTE); } @@ -1510,6 +1504,7 @@ static fix_typeE substitute(fix_typeE want) burpc(&g_new, '\\'); c = *++P; } } + burpc(&g_new, c); } done: @@ -1936,7 +1931,7 @@ log_deactivate(); resultP = NULL; arrayPP = brace_expand((char *) startP); // calling bash API here if (arrayPP) { - // Transform array into a pythonic list: [x, y, z] + // Transform array into a pythonic list: [x, y, z] in g_braced if (arrayPP[0]) { fix_typeE single_want_type; int i;