Compreendendo o Princípio Aberto Fechado

Eu fui refatoração algum código antigo de um analisador de arquivo de script simples quando me deparei com o seguinte código:

StringReader reader = new StringReader(scriptTextToProcess);
StringBuilder scope = new StringBuilder();
string line = reader.ReadLine();
while (line != null)
{
    switch (line[0])
    {
        case '$':
            // Process the entire "line" as a variable, 
            // i.e. add it to a collection of KeyValuePair.
            AddToVariables(line);
            break;
        case '!':
            // Depending of what comes after the '!' character, 
            // process the entire "scope" and/or the command in "line".
            if (line == "!execute")
                ExecuteScope(scope);
            else if (line.StartsWith("!custom_command"))
                RunCustomCommand(line, scope);
            else if (line == "!single_line_directive")
                ProcessDirective(line);

            scope = new StringBuilder();
            break;

        default:
            // No processing directive, i.e. add the "line" 
            // to the current scope.
            scope.Append(line);
            break;
    }

    line = reader.ReadLine();
}

Este simples processador de scripts parece-me um bom candidato para refatoração aplicando o "princípio aberto e fechado". As linhas começando com um$ provavelmente nunca será tratado de maneira diferente. Mas e se novas diretivas começando com um! precisa ser adicionado? Ou são necessários novos identificadores de processamento (por exemplo, novos casos de switch)?

O problema é que não consegui descobrir como adicionar mais diretivas e processadores de maneira fácil e correta sem interromper o OCP. O! -case usandoscope e / ou line torna um pouco complicado, assim como odefault-caso

Alguma sugestão