我在Sprache存储库中发现了以下代码:
Parser<string> identifier =
from leading in Parse.WhiteSpace.Many()
from first in Parse.Letter.Once().Text()
from rest in Parse.LetterOrDigit.Many().Text()
from trailing in Parse.WhiteSpace.Many()
select first + rest;
var id = identifier.Parse(" abc123 ");
我在这里看到一个矛盾:from
条款文档说源(在我们的情况下是Parse.WhiteSpace.Many()
或Parse.Letter.Once().Text()
)必须是IEnumerable
:
from子句中引用的数据源必须为
IEnumerable
、IEnumerable<T>
或IQueryable<T>
等派生类型
但它不是,编译器说这很好!
我认为有一些隐式转换到IEnumerable,但没有:Parse.WhiteSpace.Many()
返回Parser<IEnumerable<T>>
和Parse.Letter.Once().Text()
返回Parser<string>
(类型不是IEnumerable
)。
问题1:为什么编译器允许这个代码?
同样,最终表达式select first + rest
没有考虑到leading
和trailing
变量,但最终结果identifier
肯定在内部使用了它们。
第二问题:leading
和trailing
变量被添加到identifier
中的规则机制是什么?
注:如果有人能分享一份关于LINQ查询语法内部工作的包罗一切的文档,那就太好了。我没有找到关于这个话题的资料。
看了大约五分钟的代码后,我观察到:
-
解析器是返回中间结果的委托
public delegate IResult<T> Parser<out T>(IInput input);
-
声明了符合linq的方法,允许linq语法,如:
public static Parser<U> Select<T, U>(this Parser<T> parser, Func<T, U> convert) { if (parser == null) throw new ArgumentNullException(nameof(parser)); if (convert == null) throw new ArgumentNullException(nameof(convert)); return parser.Then(t => Return(convert(t))); }
https://github.com/sprache/Sprache/blob/develop/src/Sprache/Parse.cs L357
语法from x in set
工作不需要IEnumerable
接口,你只需要特定的扩展方法,具有正确的名称,接受正确的参数集。所以上面的代码使select有效。这里是where方法
public static Parser<T> Where<T>(this Parser<T> parser, Func<T, bool> predicate)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
return i => parser(i).IfSuccess(s =>
predicate(s.Value) ? s : Result.Failure<T>(i,
$"Unexpected {s.Value}.",
new string[0]));
}
https://github.com/sprache/Sprache/blob/develop/src/Sprache/Parse.cs L614
等等
这是linq抽象的单独实现,与集合无关,它是关于解析文本的。它生成一个嵌套的委托链,该链处理给定的字符串以验证它是否符合特定的语法,并返回描述已解析文本的结构。
回答第一个问题。对于第二个,您需要遵循以下代码:除了第一个之外,所有from x in set
都映射到SelectMany
函数:
public static Parser<V> SelectMany<T, U, V>(
this Parser<T> parser,
Func<T, Parser<U>> selector,
Func<T, U, V> projector)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (selector == null) throw new ArgumentNullException(nameof(selector));
if (projector == null) throw new ArgumentNullException(nameof(projector));
return parser.Then(t => selector(t).Select(u => projector(t, u)));
}
https://github.com/sprache/Sprache/blob/develop/src/Sprache/Parse.cs L635
和Then方法https://github.com/sprache/Sprache/blob/develop/src/Sprache/Parse.cs L241
中,您将看到,如果第一个成功(匹配的前导空格),则只有第二个(单字母解析器)应用于字符串的其余部分。因此,它不是一个集合处理,而是导致解析字符串的一系列事件。