如何分辨+的输入和输出之间的区别?

时间:2019-06-08 01:04:26

标签: c# parsing

我正在制作一个简单的编译器,并且正在进行字符串解析。目前,我的代码是:

    while (stringToParse.Contains(" + ") || stringToParse.Contains("+ ") || stringToParse.Contains(" +")) {
        stringToParse = stringToParse.Replace(" +", "+").Replace("+ ", "+").Replace(" + ", "+");
    }
    string[] splitString = stringToParse.Split("+");

但是类似:

"\"hello \" + \"world \" + \" + \" + \"hello\""

会返回:

["\"hello "\", "\"world \"", "\"", "\"", ]

(不带反斜杠)

但是类似:

""hello " + "world " + " + " + "hello""

会返回:

[""hello "", ""world "", """, """, ]

那么如何指定" + "是字符串还是分隔符?也许有一种方法可以检测到以下情况?

...(any number of non " or + characters)...+...(any number of " or + characters)

我的预期输出是:

[""hello "", ""world "", ""+""]

1 个答案:

答案 0 :(得分:4)


显式状态机

为此,不使用任何专用库,我建议构建一个状态机。

您将遍历字符串的字符,并根据遇到的字符更新计算机的状态。 可以进行优化,但让我们从传统的角度出发。

var characters = input.ToCharArray();
var results = new List<string>();
var current = string.Empty;

// 0 = not inside quotes, we expect +
// 1 = not inside quotes, we expect "
// 2 = inside quotes
var state = 1;

foreach (var character in characters)
{
    switch (state)
    {
        case 0:
            // We are not inside quotes, we expect +
            if (character == '+')
            {
                state = 1;
                continue;
            }
            if (char.IsWhiteSpace(character))
            {
                continue;
            }
            // error?
            break;
        case 1:
            // We are not inside quotes, we expect "
            if (character == '\"')
            {
                state = 2;
                continue;
            }
            if (char.IsWhiteSpace(character))
            {
                continue;
            }
            // error?
            break;
        case 2:
            // We are inside quotes, we expect "
            if (character == '\"')
            {
                state = 0;
                results.Add(current);
                current = string.Empty;
                continue;
            }
            current += character;
            break;
        default:
            // error?
            break;
    }
}

if (state != 0)
{
    // error
}

// You can use results.ToArray();

可能的优化:

  • 我们可以使用StringBuilder代替串联。
  • 此外,我们可以使用IndexOf查找下一个相关字符。
  • 我们可以检查一个字符串(一串字符)是否为空或空格(也许使用IsNullOrWhiteSpace)。
  • 我们可以使用AsSpan,所以我们可以改为使用ReadOnlySpan

您还可以看到如何为自己的转义序列或任何其他内容添加支持。


隐式状态机(带有帮助程序类)

我想指出,这不是组织此代码的唯一方法。如果您是我,我将创建一个具有两个方法的伪迭代器类:

  • 一种返回下一个字符的方法……或更妙的是,如果下一个字符与参数匹配(并且前进),则返回true,否则返回false(并且不前进)。
  • 一种方法,它返回所有字符,直到特定字符的下一个实例为止(并前进到那里)。

这种方法的主要优点是,我不再需要一个一个字符一个字符地步进,因此,我不需要state变量。相反,我可以允许代码结构类似于我的语法。

等等,我已经编写了此类:StringProcessor。它是Theraot.Core nuget的一部分,用于将字符串解析为BigInteger。

var processor = new Theraot.Core.StringProcessor(input);
var results = new List<string>();

while (!processor.EndOfString)
{
    // SkipWhile skips all the characters that match
    processor.SkipWhile(char.IsWhiteSpace);
    // Read returns true (and advances after) if what is next matches the paramter
    if (processor.Read('"'))
    {
        // ReadUntil advances after and returns everything found before the parameter 
        // Note: it does not advance after the parameter.
        results.Add(processor.ReadUntil('"'));
        processor.Read('"'); 
    }
    processor.SkipWhile(char.IsWhiteSpace);
    if (!processor.Read('+'))
    {
        // error?
    }
}

请注意,上面使用的诸如StringProcessor之类的类可以减少很多毛病,这使其对于简单语言而言是可行的。


自定义令牌生成器

当然,对于更复杂的东西,您可能需要寻找令牌生成器。

举一个例子,认为这是我们拥有的“语法”:

Document: Many
{
    Whitespace
    String:
    {
        QuoteSymbol
        NonQuoteSymbol
        QuoteSymbol
    }
    Whitespace
    PlusSymbol
}

不,这不是任何常用的元语言。但是,以这种方式编写的代码很容易看到我们上面的代码与语言的相似之处。

写如下代码不好吗?

var QuoteSymbol = Pattern.Literal("QuoteSymbol", '"');
var NonQuoteSymbol = Pattern.Custom("NonQuoteSymbol", s => s.ReadUntil('"'));
var String = Pattern.Conjunction("String", QuoteSymbol, NonQuoteSymbol, QuoteSymbol);

var WhiteSpace = Pattern.Custom("WhiteSpace", s => s.ReadWhile(char.IsWhiteSpace));
var PlusSymbol = Pattern.Literal("PlusSymbol", '+');
var Document = Pattern.Repetition(
    Pattern.Conjunction(WhiteSpace, String, WhiteSpace, PlusSymbol)
);

var results = from TerminalSymbol symbol
              in Document.Parse(input)
              where symbol.Pattern == String
              select symbol.ToString();

编写这样的代码将使修改语言变得更加容易。 好吧,我们仍在编写代码,但是您可以想象解析一个文件,该文件具有您要解析的语言的语法...花哨!

如您所料,需要额外的工作来构建必要的代码才能使其正常工作。或者,您知道获得some code that already works(链接的代码围绕StringProcessor构建)。


语言工具包

前面介绍的代码不适合用于prettyprinter,并且无法从语法错误中恢复。 可以将其修改为执行此类操作。也不与任何级别的代码编辑器集成。

如果您需要完整的解决方案。我有两个建议:

如果您想在顶部创建编程语言,就会使用这些东西。


当然,我应该将您链接到"Compilers: Principles, Techniques, and Tools",通常被称为“龙书”。