4

I have string which have a special substrings in a special format:

$( variableName )

And this pattern can repeat nested many times:

$( variableName $( anotherVariableName ) )

Such as: [ A test string ]

Here is a test string contain $(variableA) and $(variableB$(variableC))

For this test string Let assume that

$(variableA) = A, $(variableB) = B, $(variableC) = C, $(variableBC) = Y

What I want is replace those special patters -> $(variableName) with actual values such as the resulting string should be:

Here is a test string contain A and Y

Any suggestion for generic and elegant solution?

Hippias Minor
  • 1,917
  • 2
  • 21
  • 46
  • So for your sintax $(variableB$(variableC)) is evaluated as $(variableBC) ? – Mauricio Gracia Gutierrez Aug 28 '13 at 15:11
  • You typically do this with a custom parser, which won't be particularly generic nor elegant. – Jim Mischel Aug 28 '13 at 15:13
  • @MauricioGracia: Yes, you can do it with multiple calls to a `Replace` method. Or you can write a simple parser. The parser will typically be faster, and it's not terribly difficult to build. But your mileage may vary. – Jim Mischel Aug 28 '13 at 15:29
  • @MauricioGracia The fact that the language is very simple does not mean that it cannot benefit from a parser. What it usually means is that the parser is going to be very simple as well ;-) – Sergey Kalinichenko Aug 28 '13 at 15:36
  • @JimMischel Although you're definitely right about the parsers not being generic, their elegance is in the eye of the beholder. – Sergey Kalinichenko Aug 28 '13 at 15:41
  • @MauricioGracia Neither the number of control statements nor the use of a particular data structure makes a parser what it is. If something takes a structured text and "understands" its structure, it is a parser. The power of this particular "switch with three cases" comes from a single line in the middle: `var name = Replace(input, ref pos, vars, true);` This is what makes this piece of code a recursive descent parser. – Sergey Kalinichenko Aug 28 '13 at 15:48
  • $(variableB$(variableC)) is evaluated as $(variableBC) – Hippias Minor Aug 29 '13 at 10:03

1 Answers1

3

Here is a straightforward solution performing a recursive descent parsing:

public static string Replace(
    string input
,   ref int pos
,   IDictionary<string,string> vars
,   bool stopOnClose = false
) {
    var res = new StringBuilder();
    while (pos != input.Length) {
        switch (input[pos]) {
            case '\\':
                pos++;
                if (pos != input.Length) {
                    res.Append(input[pos++]);
                }
                break;
            case ')':
                if (stopOnClose) {
                    return res.ToString();
                }
                res.Append(')');
                pos++;
                break;
            case '$':
                pos++;
                if (pos != input.Length && input[pos] == '(') {
                    pos++;
                    var name = Replace(input, ref pos, vars, true);
                    string replacement;
                    if (vars.TryGetValue(name, out replacement)) {
                        res.Append(replacement);
                    } else {
                        res.Append("<UNKNOWN:");
                        res.Append(name);
                        res.Append(">");
                    }
                    pos++;
                } else {
                    res.Append('$');
                }
                break;
            default:
                res.Append(input[pos++]);
                break;
        }
    }
    return res.ToString();
}
public static void Main() {
    const string input = "Here is a test string contain $(variableA) and $(variableB$(variableC))";
    var vars = new Dictionary<string, string> {
        {"variableA", "A"}, {"variableB", "B"}, {"variableC", "C"}, {"variableBC", "Y"}
    };
    int pos = 0;
    Console.WriteLine(Replace(input, ref pos, vars));
}

This solution reuses the implementation of Replace to built the name of the variable that you would like to replace by calling itself with the stopOnClose flag set to true. Top-level invocation does not stop on reaching the ')' symbol, letting you use it unescaped.

Here is a demo on ideone.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523