private static string EvaluateInternal(
string format, ref int pos, FormatParam[] args, Stack<FormatParam> stack,
ref FormatParam[] dynamicVars, ref FormatParam[] staticVars)
{
// Create a StringBuilder to store the output of this processing. We use the format's length as an
// approximation of an upper-bound for how large the output will be, though with parameter processing,
// this is just an estimate, sometimes way over, sometimes under.
StringBuilder output = StringBuilderCache.Acquire(format.Length);
// Format strings support conditionals, including the equivalent of "if ... then ..." and
// "if ... then ... else ...", as well as "if ... then ... else ... then ..."
// and so on, where an else clause can not only be evaluated for string output but also
// as a conditional used to determine whether to evaluate a subsequent then clause.
// We use recursion to process these subsequent parts, and we track whether we're processing
// at the same level of the initial if clause (or whether we're nested).
bool sawIfConditional = false;
// Process each character in the format string, starting from the position passed in.
for (; pos < format.Length; pos++)
{
// '%' is the escape character for a special sequence to be evaluated.
// Anything else just gets pushed to output.
if (format[pos] != '%')
{
output.Append(format[pos]);
continue;
}
// We have a special parameter sequence to process. Now we need
// to look at what comes after the '%'.
++pos;
switch (format[pos])
{
// Output appending operations
case '%': // Output the escaped '%'
output.Append('%');
break;
case 'c': // Pop the stack and output it as a char
output.Append((char)stack.Pop().Int32);
break;
case 's': // Pop the stack and output it as a string
output.Append(stack.Pop().String);
break;
case 'd': // Pop the stack and output it as an integer
output.Append(stack.Pop().Int32);
break;
case 'o':
case 'X':
case 'x':
case ':':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// printf strings of the format "%[[:]flags][width[.precision]][doxXs]" are allowed
// (with a ':' used in front of flags to help differentiate from binary operations, as flags can
// include '-' and '+'). While above we've special-cased common usage (e.g. %d, %s),
// for more complicated expressions we delegate to printf.
int printfEnd = pos;
for (; printfEnd < format.Length; printfEnd++) // find the end of the printf format string
{
char ec = format[printfEnd];
if (ec == 'd' || ec == 'o' || ec == 'x' || ec == 'X' || ec == 's')
{
break;
}
}
if (printfEnd >= format.Length)
{
throw new InvalidOperationException(SR.IO_TermInfoInvalid);
}
string printfFormat = format.Substring(pos - 1, printfEnd - pos + 2); // extract the format string
if (printfFormat.Length > 1 && printfFormat[1] == ':')
{
printfFormat = printfFormat.Remove(1, 1);
}
output.Append(FormatPrintF(printfFormat, stack.Pop().Object)); // do the printf formatting and append its output
break;
// Stack pushing operations
case 'p': // Push the specified parameter (1-based) onto the stack
pos++;
Debug.Assert(format[pos] >= '0' && format[pos] <= '9');
stack.Push(args[format[pos] - '1']);
break;
case 'l': // Pop a string and push its length
stack.Push(stack.Pop().String.Length);
break;
case '{': // Push integer literal, enclosed between braces
pos++;
int intLit = 0;
while (format[pos] != '}')
{
Debug.Assert(format[pos] >= '0' && format[pos] <= '9');
intLit = (intLit * 10) + (format[pos] - '0');
pos++;
}
stack.Push(intLit);
break;
case '\'': // Push literal character, enclosed between single quotes
stack.Push((int)format[pos + 1]);
Debug.Assert(format[pos + 2] == '\'');
pos += 2;
break;
// Storing and retrieving "static" and "dynamic" variables
case 'P': // Pop a value and store it into either static or dynamic variables based on whether the a-z variable is capitalized
pos++;
int setIndex;
FormatParam[] targetVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out setIndex);
targetVars[setIndex] = stack.Pop();
break;
case 'g': // Push a static or dynamic variable; which is based on whether the a-z variable is capitalized
pos++;
int getIndex;
FormatParam[] sourceVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out getIndex);
stack.Push(sourceVars[getIndex]);
break;
// Binary operations
case '+':
case '-':
case '*':
case '/':
case 'm':
case '^': // arithmetic
case '&':
case '|': // bitwise
case '=':
case '>':
case '<': // comparison
case 'A':
case 'O': // logical
int second = stack.Pop().Int32; // it's a stack... the second value was pushed last
int first = stack.Pop().Int32;
char c = format[pos];
stack.Push(
c == '+' ? (first + second) :
c == '-' ? (first - second) :
c == '*' ? (first * second) :
c == '/' ? (first / second) :
c == 'm' ? (first % second) :
c == '^' ? (first ^ second) :
c == '&' ? (first & second) :
c == '|' ? (first | second) :
c == '=' ? AsInt(first == second) :
c == '>' ? AsInt(first > second) :
c == '<' ? AsInt(first < second) :
c == 'A' ? AsInt(AsBool(first) && AsBool(second)) :
c == 'O' ? AsInt(AsBool(first) || AsBool(second)) :
0); // not possible; we just validated above
break;
// Unary operations
case '!':
case '~':
int value = stack.Pop().Int32;
stack.Push(
format[pos] == '!' ? AsInt(!AsBool(value)) :
~value);
break;
// Some terminfo files appear to have a fairly liberal interpretation of %i. The spec states that %i increments the first two arguments,
// but some uses occur when there's only a single argument. To make sure we accommodate these files, we increment the values
// of up to (but not requiring) two arguments.
case 'i':
if (args.Length > 0)
{
args[0] = 1 + args[0].Int32;
if (args.Length > 1)
args[1] = 1 + args[1].Int32;
}
break;
// Conditional of the form %? if-part %t then-part %e else-part %;
// The "%e else-part" is optional.
case '?':
sawIfConditional = true;
break;
case 't':
// We hit the end of the if-part and are about to start the then-part.
// The if-part left its result on the stack; pop and evaluate.
bool conditionalResult = AsBool(stack.Pop().Int32);
// Regardless of whether it's true, run the then-part to get past it.
// If the conditional was true, output the then results.
pos++;
string thenResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
if (conditionalResult)
{
output.Append(thenResult);
}
Debug.Assert(format[pos] == 'e' || format[pos] == ';');
// We're past the then; the top of the stack should now be a Boolean
// indicating whether this conditional has more to be processed (an else clause).
if (!AsBool(stack.Pop().Int32))
{
// Process the else clause, and if the conditional was false, output the else results.
pos++;
string elseResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
if (!conditionalResult)
{
output.Append(elseResult);
}
// Now we should be done (any subsequent elseif logic will have been handled in the recursive call).
if (!AsBool(stack.Pop().Int32))
{
throw new InvalidOperationException(SR.IO_TermInfoInvalid);
}
}
// If we're in a nested processing, return to our parent.
if (!sawIfConditional)
{
stack.Push(1);
return StringBuilderCache.GetStringAndRelease(output);
}
// Otherwise, we're done processing the conditional in its entirety.
sawIfConditional = false;
break;
case 'e':
case ';':
// Let our caller know why we're exiting, whether due to the end of the conditional or an else branch.
stack.Push(AsInt(format[pos] == ';'));
return StringBuilderCache.GetStringAndRelease(output);
// Anything else is an error
default:
throw new InvalidOperationException(SR.IO_TermInfoInvalid);
}
}
stack.Push(1);
return StringBuilderCache.GetStringAndRelease(output);
}