feat: Parser Combinator库和词法分析器 (#2)
All checks were successful
Run unit test / Unit-Test (push) Successful in 41s

Reviewed-on: https://git.bupt-hpc.cn/jackfiled/CanonSharp/pulls/2
Co-authored-by: jackfiled <xcrenchangjun@outlook.com>
Co-committed-by: jackfiled <xcrenchangjun@outlook.com>
This commit is contained in:
jackfiled 2024-08-13 14:46:11 +08:00 committed by 任昌骏
parent 57c31ec435
commit 3ed8bf5d36
68 changed files with 3133 additions and 1068 deletions

View File

@ -0,0 +1,49 @@
namespace CanonSharp.Combinator.Abstractions;
/// <summary>
/// 失败解析结果基类
/// </summary>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果类型</typeparam>
public abstract class FailedResult<TToken, T> : ParseResult<TToken, T>
{
public override T Value => throw Exception;
/// <summary>
/// 当前读取到的状态
/// </summary>
public abstract IReadState<TToken> State { get; }
/// <summary>
/// 解析失败的消息
/// </summary>
public abstract string Message { get; }
/// <summary>
/// 解析失败的异常
/// </summary>
public virtual ParseException Exception => new(ToString());
/// <summary>
/// 转换该失败结果的类型
/// </summary>
/// <typeparam name="TNext">转换之后的结果类型</typeparam>
/// <returns>转换之后的失败解析类型</returns>
public abstract FailedResult<TToken, TNext> Convert<TNext>();
internal override ParseResult<TToken, TResult> Next<TNext, TResult>(Func<T, Parser<TToken, TNext>> nextParser,
Func<ParseResult<TToken, TNext>, ParseResult<TToken, TResult>> continuation)
=> continuation(Convert<TNext>());
public override ParseResult<TToken, TResult> Map<TResult>(Func<T, TResult> map)
=> Convert<TResult>();
public override TResult CaseOf<TResult>(Func<SuccessfulResult<TToken, T>, TResult> successfulHandler,
Func<FailedResult<TToken, T>, TResult> failedHandler)
=> failedHandler(this);
public override string ToString()
{
return $"Parse Failed: {Message}.";
}
}

View File

@ -0,0 +1,26 @@
namespace CanonSharp.Combinator.Abstractions;
/// <summary>
/// 输入流的读取状态
/// </summary>
/// <typeparam name="TToken">输入流元素类型</typeparam>
public interface IReadState<out TToken>
{
public TToken Current { get; }
public bool HasValue { get; }
}
/// <summary>
/// 输入流的读取状态
/// </summary>
/// <typeparam name="TToken">输入流元素类型</typeparam>
/// <typeparam name="TState">下一个读取状态的类型</typeparam>
public interface IReadState<out TToken, TState> : IReadState<TToken>, IEquatable<TState>
where TState : IReadState<TToken, TState>
{
/// <summary>
/// 下一个读取状态
/// </summary>
TState Next { get; }
}

View File

@ -0,0 +1,48 @@
namespace CanonSharp.Combinator.Abstractions;
/// <summary>
/// 解析器结果
/// </summary>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">实际结果类型</typeparam>
public abstract class ParseResult<TToken, T>
{
/// <summary>
/// 实际结果对象
/// </summary>
public abstract T Value { get; }
protected ParseResult()
{
}
/// <summary>
/// 在当前结果上应用下一个解析器
/// </summary>
/// <param name="nextParser">下一个解析器的函数</param>
/// <param name="continuation">处理解析结果的后继函数</param>
/// <typeparam name="TNext">下一个解析器函数返回的解析结果类型</typeparam>
/// <typeparam name="TResult">最终的解析结果类型</typeparam>
/// <returns></returns>
internal abstract ParseResult<TToken, TResult> Next<TNext, TResult>(Func<T, Parser<TToken, TNext>> nextParser,
Func<ParseResult<TToken, TNext>, ParseResult<TToken, TResult>> continuation);
/// <summary>
/// 映射结果
/// </summary>
/// <param name="map">映射结果的函数</param>
/// <typeparam name="TResult">映射结果函数返回解析结果的类型</typeparam>
/// <returns>最终的解析结果</returns>
public abstract ParseResult<TToken, TResult> Map<TResult>(Func<T, TResult> map);
/// <summary>
/// 在成功或者失败解析结果上应用不同的后继函数
/// </summary>
/// <param name="successfulHandler">在成功解析结果上应用的函数</param>
/// <param name="failedHandler">在失败解析结构上应用的函数</param>
/// <typeparam name="TResult">最后返回解析结果的类型</typeparam>
/// <returns>最后的解析结果</returns>
public abstract TResult CaseOf<TResult>(Func<SuccessfulResult<TToken, T>, TResult> successfulHandler,
Func<FailedResult<TToken, T>, TResult> failedHandler);
}

View File

@ -0,0 +1,43 @@
using CanonSharp.Combinator.Extensions;
namespace CanonSharp.Combinator.Abstractions;
/// <summary>
/// 解析器抽象基类
/// </summary>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果类型</typeparam>
public abstract class Parser<TToken, T>
{
/// <summary>
/// 解析器运行函数
/// </summary>
/// <param name="state">解析的输入流状态</param>
/// <param name="continuation">运行之后的后继函数</param>
/// <typeparam name="TState">输入流状态类型</typeparam>
/// <typeparam name="TResult">后继函数运行之后的解析结果类型</typeparam>
/// <returns></returns>
internal abstract ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
where TState : IReadState<TToken, TState>;
public ParseResult<TToken, T> Parse<TState>(TState state) where TState : IReadState<TToken, TState>
{
return Run(state);
}
private ParseResult<TToken, T> Run<TState>(TState state) where TState : IReadState<TToken, TState>
{
try
{
return Run(state, result => result);
}
catch (Exception e)
{
return ParseResultBuilder.Fail<TToken, TState, T>(e, state);
}
}
public static Parser<TToken, T> operator |(Parser<TToken, T> a, Parser<TToken, T> b)
=> a.Alternative(b);
}

View File

@ -0,0 +1,33 @@
namespace CanonSharp.Combinator.Abstractions;
/// <summary>
/// 成功解析结果基类
/// </summary>
/// <param name="value">实际的解析结果</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">实际的解析结果类型</typeparam>
public abstract class SuccessfulResult<TToken, T>(T value) : ParseResult<TToken, T>
{
public override T Value => value;
/// <summary>
/// 运行下一个解析器
/// </summary>
/// <param name="parser">下一个解析器</param>
/// <param name="continuation">处理解析结果的后继函数</param>
/// <typeparam name="TNext">下一个解析器返回的结果类型</typeparam>
/// <typeparam name="TResult">最终的结果类型</typeparam>
/// <returns>最终的结果</returns>
protected abstract ParseResult<TToken, TResult> RunNext<TNext, TResult>(Parser<TToken, TNext> parser,
Func<ParseResult<TToken, TNext>, ParseResult<TToken, TResult>> continuation);
internal override ParseResult<TToken, TResult> Next<TNext, TResult>(Func<T, Parser<TToken, TNext>> nextParser,
Func<ParseResult<TToken, TNext>, ParseResult<TToken, TResult>> continuation)
=> RunNext(nextParser(Value), continuation);
public override TResult CaseOf<TResult>(Func<SuccessfulResult<TToken, T>, TResult> successfulHandler,
Func<FailedResult<TToken, T>, TResult> failedHandler)
=> successfulHandler(this);
public override string ToString() => Value?.ToString() ?? string.Empty;
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,572 @@
using System.Runtime.CompilerServices;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Parsers.Bases;
using CanonSharp.Combinator.Parsers.Modifiers;
using static CanonSharp.Combinator.ParserBuilder;
namespace CanonSharp.Combinator.Extensions;
public static class ParserExtensions
{
#region BasesParser
/// <summary>
/// 选择组合子
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Alternative<TToken, T>(this Parser<TToken, T> first, Parser<TToken, T> second)
=> new AlternativeParser<TToken, T>(first, second);
/// <summary>
/// 选择组合子
/// 按照失败的解析结果选择第二个解析器
/// </summary>
/// <param name="parser"></param>
/// <param name="resume"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Alternative<TToken, T>(this Parser<TToken, T> parser,
Func<FailedResult<TToken, T>, Parser<TToken, T>> resume)
=> new ResumeParser<TToken, T>(parser, resume);
/// <summary>
/// 单子解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="next">按照输出指定下一个解析器的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Bind<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, Parser<TToken, TResult>> next)
=> new BindParser<TToken, T, TResult>(parser, next);
/// <summary>
/// 映射解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="map">按照输出指定结果的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Map<TToken, T, TResult>(this Parser<TToken, T> parser, Func<T, TResult> map)
=> new MapParser<TToken, T, TResult>(parser, map);
/// <summary>
/// 映射解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="result">最后的输出结果</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Map<TToken, T, TResult>(this Parser<TToken, T> parser, TResult result)
=> parser.Map(_ => result);
/// <summary>
/// 下一个解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="next">输入成功结果输出下一个解析器的函数</param>
/// <param name="failedNext">输入失败结果输出下一个解析器的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Next<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, Parser<TToken, TResult>> next,
Func<FailedResult<TToken, T>, Parser<TToken, TResult>> failedNext)
=> new NextParser<TToken, T, TResult>(parser, next, failedNext);
/// <summary>
/// 下一个解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="next">输入成功结果输出下一个解析器的函数</param>
/// <param name="failedHandler">输出失败结果输出后续结果的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Next<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, Parser<TToken, TResult>> next, Func<FailedResult<TToken, T>, TResult> failedHandler)
=> parser.Next(next, failedResult => Pure<TToken, TResult>(failedHandler(failedResult)));
/// <summary>
/// 下一个解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="next">输出成功结果输出下一个解析器的函数</param>
/// <param name="failedResult">如果失败之后返回该结果</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Next<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, Parser<TToken, TResult>> next, TResult failedResult)
=> parser.Next(next, _ => Pure<TToken, TResult>(failedResult));
/// <summary>
/// 下一个解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="nextResult">输入成功结果返回新的结果</param>
/// <param name="failedResume">输入失败结果返回下一个解析器的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Next<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, TResult> nextResult, Func<FailedResult<TToken, T>, Parser<TToken, TResult>> failedResume)
=> parser.Next(x => Pure<TToken, TResult>(nextResult(x)), failedResume);
/// <summary>
/// 下一个解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="nextResult">输入成功结果返回新的结果</param>
/// <param name="failedResult">输入失败结果返回新的结果</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Next<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, TResult> nextResult, Func<FailedResult<TToken, T>, TResult> failedResult)
=> new SuccessfulMapParser<TToken, T, TResult>(parser, nextResult, failedResult);
/// <summary>
/// 下一个解析器组合子
/// </summary>
/// <param name="parser"></param>
/// <param name="successfulHandler">输入成功结果返回新结果的函数</param>
/// <param name="failedResult">返回的失败结果</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Next<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, TResult> successfulHandler, TResult failedResult)
=> parser.Next(successfulHandler, _ => failedResult);
#endregion
#region ModifiedParser
/// <summary>
/// 在解析结果上执行指定操作
/// </summary>
/// <param name="parser"></param>
/// <param name="successfulAction">成功结果上执行的操作</param>
/// <param name="failedAction">失败结果上执行的操作</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Do<TToken, T>(this Parser<TToken, T> parser, Action<T> successfulAction,
Action<FailedResult<TToken, T>> failedAction)
=> new DoParser<TToken, T>(parser, successfulAction, failedAction);
/// <summary>
/// 在解析结果上执行指定的操作
/// </summary>
/// <param name="parser"></param>
/// <param name="successfulAction">成功结果上执行的操作</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Do<TToken, T>(this Parser<TToken, T> parser, Action<T> successfulAction)
=> parser.Do(successfulAction, _ => { });
/// <summary>
/// 向前看解析器
/// 执行解析器的同时不消耗输入流
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> LookAhead<TToken, T>(this Parser<TToken, T> parser)
=> new LookAheadParser<TToken, T>(parser);
/// <summary>
/// 翻转上游解析器的输出结果
/// </summary>
/// <param name="parser"></param>
/// <param name="result">翻转之后的输出结果</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Not<TToken, TIgnore, T>(this Parser<TToken, TIgnore> parser, T result)
=> new ReverseParser<TToken, TIgnore, T>(parser, result);
/// <summary>
/// 翻转上游解析器的输出结果
/// 输出结果默认为Unit
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, Unit> Not<TToken, TIgnore>(this Parser<TToken, TIgnore> parser)
=> parser.Not(Unit.Instance);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Try<TToken, T>(this Parser<TToken, T> parser,
Func<FailedResult<TToken, T>, T> resume)
=> new TryParser<TToken, T>(parser, resume);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Try<TToken, T>(this Parser<TToken, T> parser, T result)
=> parser.Try(_ => result);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, bool> Try<TToken, T>(this Parser<TToken, T> parser)
=> parser.Next(_ => true, false).Try(false);
#endregion
#region Combinators
/// <summary>
/// 连接两个解析器,返回左边解析器的结果
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TLeft> Left<TToken, TLeft, TRight>(this Parser<TToken, TLeft> left,
Parser<TToken, TRight> right)
=> left.Bind(right.Map);
/// <summary>
/// 连接两个解析器,返回右边解析器的结果
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TRight> Right<TToken, TLeft, TRight>(this Parser<TToken, TLeft> left,
Parser<TToken, TRight> right)
=> left.Bind(_ => right);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Parser<TToken, IEnumerable<T>> ManyRecursively<TToken, T>(this Parser<TToken, T> parser,
IEnumerable<T> result)
=> parser.Next(x => parser.ManyRecursively(result.Append(x)), result);
/// <summary>
/// 将上游解析器运行零或若干次
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Many<TToken, T>(this Parser<TToken, T> parser)
=> parser.ManyRecursively([]);
/// <summary>
/// 将上游解析器运行若干次
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Many1<TToken, T>(this Parser<TToken, T> parser)
=> parser.Bind(x => parser.ManyRecursively([x]));
/// <summary>
/// 跳过执行上游解析器运行零或若干次
/// 跳过执行不是不执行
/// 而是不返回结果
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, Unit> SkipMany<TToken, TIgnore>(this Parser<TToken, TIgnore> parser)
=> Fix<TToken, Unit>(self => parser.Next(_ => self, Unit.Instance));
/// <summary>
/// 跳过执行上游解析器运行若干次
/// 跳过执行不是不执行
/// 而是不返回结果
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, Unit> SkipMany1<TToken, TIgnore>(this Parser<TToken, TIgnore> parser)
=> parser.Right(parser.SkipMany());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Parser<TToken, T> ChainRecursively<TToken, T>(Func<T, Parser<TToken, T>> chain, T value)
=> chain(value).Next(x => ChainRecursively(chain, x), value);
/// <summary>
/// 链式解析器组合子
/// 按照解析结果决定下一个解析器
/// </summary>
/// <param name="parser"></param>
/// <param name="chain"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Chain<TToken, T>(this Parser<TToken, T> parser, Func<T, Parser<TToken, T>> chain)
=> parser.Bind(x => ChainRecursively(chain, x));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Parser<TToken, IEnumerable<T>> ManyTillRecursively<TToken, T, TIgnore>(this Parser<TToken, T> parser,
Parser<TToken, TIgnore> terminator, IEnumerable<T> result)
=> terminator.Next(_ => Pure<TToken, IEnumerable<T>>(result),
_ => parser.Bind(x => parser.ManyTillRecursively(terminator, result.Append(x))));
/// <summary>
/// 执行指定解析器直到终结解析器执行成功的组合子
/// 指定解析器可以执行零次或者多次
/// </summary>
/// <param name="parser">指定重复执行的解析器</param>
/// <param name="terminator">判断是否终结的解析器</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> ManyTill<TToken, T, TIgnore>(this Parser<TToken, T> parser,
Parser<TToken, TIgnore> terminator)
=> parser.ManyTillRecursively(terminator, []);
/// <summary>
/// 执行指定解析器直到终结解析器执行成功的组合子
/// 指定解析器至少执行一次
/// </summary>
/// <param name="parser">指定重复执行的解析器</param>
/// <param name="terminator">判断是否终结的解析器</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Many1Till<TToken, T, TIgnore>(this Parser<TToken, T> parser,
Parser<TToken, TIgnore> terminator)
=> parser.Bind(x => parser.ManyTillRecursively(terminator, [x]));
/// <summary>
/// 跳过指定解析器直到终结解析器执行成功的组合子
/// 指定解析器可以执行零次或者若干次
/// </summary>
/// <param name="parser"></param>
/// <param name="terminator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> SkipTill<TToken, TIgnore, T>(this Parser<TToken, TIgnore> parser,
Parser<TToken, T> terminator)
=> Fix<TToken, T>(self => terminator | parser.Right(self));
/// <summary>
/// 跳过指定解析器直到终结解析器执行成功的组合子
/// 指定解析器至少要执行一次
/// </summary>
/// <param name="parser"></param>
/// <param name="terminator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Skip1Till<TToken, TIgnore, T>(this Parser<TToken, TIgnore> parser,
Parser<TToken, T> terminator)
=> parser.Right(parser.SkipTill(terminator));
/// <summary>
/// 解析直到指定的解析器识别成功
/// </summary>
/// <param name="parser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Match<TToken, T>(this Parser<TToken, T> parser)
=> SkipTill(Any<TToken>(), parser);
/// <summary>
/// 在左右两个解析器指定的范围内进行解析
/// 解析类似于左右括号和左右引号类似的句式
/// </summary>
/// <param name="parser"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Quote<TToken, T, TLeft, TRight>(this Parser<TToken, T> parser,
Parser<TToken, TLeft> left, Parser<TToken, TRight> right)
=> left.Right(parser.ManyTill(right));
/// <summary>
/// 在同一个解析器指定的范围内进行解析
/// </summary>
/// <param name="parser"></param>
/// <param name="quotedParser"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TQuote"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Quote<TToken, T, TQuote>(this Parser<TToken, T> parser,
Parser<TToken, TQuote> quotedParser)
=> parser.Quote(quotedParser, quotedParser);
/// <summary>
/// 解析由分隔符解析器分割的多个符号
/// 例如a,b,c
/// 实际的解析器可以运行零次或者多次
/// </summary>
/// <param name="parser">实际的解析器</param>
/// <param name="separator">分隔符解析器</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TSeparator"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> SeparatedBy1<TToken, T, TSeparator>(this Parser<TToken, T> parser,
Parser<TToken, TSeparator> separator)
=> parser.Bind(x => separator.Right(parser).ManyRecursively([x]));
/// <summary>
/// 解析由分隔符解析器分割的多个符号
/// 例如a,b,c
/// 实际的解析器可以运行多次
/// </summary>
/// <param name="parser">实际的解析器</param>
/// <param name="separator">分隔符解析器</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TSeparator"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> SeparatedBy<TToken, T, TSeparator>(this Parser<TToken, T> parser,
Parser<TToken, TSeparator> separator)
=> parser.SeparatedBy1(separator).Try([]);
/// <summary>
/// 解析直到使用分隔符解析器结束
/// 例如abc.
/// 实际的解析器可以运行零次或者多次
/// </summary>
/// <param name="parser"></param>
/// <param name="separator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TSeparator"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> EndBy<TToken, T, TSeparator>(this Parser<TToken, T> parser,
Parser<TToken, TSeparator> separator)
=> parser.Many().Left(separator);
/// <summary>
/// 解析直到使用分隔符解析器结束
/// 例如abc.
/// 实际的解析器至少运行一次
/// </summary>
/// <param name="parser"></param>
/// <param name="separator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TSeparator"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> EndBy1<TToken, T, TSeparator>(this Parser<TToken, T> parser,
Parser<TToken, TSeparator> separator)
=> parser.Many1().Left(separator);
/// <summary>
/// Separated和End的综合体
/// 形如a,b,c,
/// 实际的解析器至少运行一次
/// </summary>
/// <param name="parser"></param>
/// <param name="separator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TSeparator"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> SeparatedOrEndBy1<TToken, T, TSeparator>(this Parser<TToken, T> parser,
Parser<TToken, TSeparator> separator)
=> parser.SeparatedBy1(separator).Left(separator.Try());
/// <summary>
/// Separated和End的综合体
/// 形如a,b,c,
/// 实际的解析器可以运行零次或者多次
/// </summary>
/// <param name="parser"></param>
/// <param name="separator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TSeparator"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> SeparatedOrEndBy<TToken, T, TSeparator>(this Parser<TToken, T> parser,
Parser<TToken, TSeparator> separator)
=> parser.SeparatedOrEndBy1(separator).Try([]);
#endregion
#region LINQ
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> Select<TToken, T, TResult>(this Parser<TToken, T> parser,
Func<T, TResult> selector)
=> parser.Map(selector);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TResult> SelectMany<TToken, T, TIntermediate, TResult>(this Parser<TToken, T> parser,
Func<T, Parser<TToken, TIntermediate>> selector, Func<T, TIntermediate, TResult> projector)
=> parser.Bind(x => selector(x).Map(y => projector(x, y)));
#endregion
}

View File

@ -0,0 +1,18 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Extensions;
public static class ReadStateExtensions
{
public static IEnumerable<TState> AsEnumerable<TToken, TState>(this TState source)
where TState : IReadState<TToken, TState>
{
TState current = source;
while (current.HasValue)
{
yield return current;
current = current.Next;
}
}
}

View File

@ -0,0 +1,15 @@
namespace CanonSharp.Combinator;
/// <summary>
/// 解析过程中的异常
/// </summary>
public class ParseException : Exception
{
public ParseException(string message) : base(message)
{
}
public ParseException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@ -0,0 +1,67 @@
using System.Runtime.CompilerServices;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Results;
namespace CanonSharp.Combinator;
/// <summary>
/// <see cref="T:CanonSharp.Combinator.Abstractions.ParseResult"/> 相关的扩展方法
/// </summary>
public static class ParseResultBuilder
{
/// <summary>
/// 生成解析成功的结果
/// </summary>
/// <param name="value">解析成功的对象</param>
/// <param name="state">下一个输入流状态</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TState">输入流状态类型</typeparam>
/// <typeparam name="T">解析成功的对象类型</typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ParseResult<TToken, T> Succeed<TToken, TState, T>(T value, TState state)
where TState : IReadState<TToken, TState>
=> new InternalSuccessfulResult<TToken, TState, T>(value, state);
/// <summary>
/// 生成错误类型的解析失败结果
/// </summary>
/// <param name="state">解析的输入流状态</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TState">输入流状态类型</typeparam>
/// <typeparam name="T">解析成功的对象类型</typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ParseResult<TToken, T> Fail<TToken, TState, T>(TState state)
where TState : IReadState<TToken, TState>
=> new FailedResultWithError<TToken, TState, T>(state);
/// <summary>
/// 生成消息类型的解析失败结果
/// </summary>
/// <param name="message">错误消息</param>
/// <param name="state">输入流状态</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TState">输入流状态类型</typeparam>
/// <typeparam name="T">解析成功的对象类型</typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ParseResult<TToken, T> Fail<TToken, TState, T>(string message, TState state)
where TState : IReadState<TToken, TState>
=> new FailedResultWithMessage<TToken, TState, T>(message, state);
/// <summary>
/// 生成异常类型的解析失败结果
/// </summary>
/// <param name="exception">解析异常</param>
/// <param name="state">输入流状态</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TState">输入流状态类型</typeparam>
/// <typeparam name="T">解析成功的对象类型</typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ParseResult<TToken, T> Fail<TToken, TState, T>(Exception exception, TState state)
where TState : IReadState<TToken, TState>
=> new FailedResultWithException<TToken, TState, T>(exception, state);
}

View File

@ -0,0 +1,223 @@
using System.Runtime.CompilerServices;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Combinator.Parsers.Bases;
using CanonSharp.Combinator.Parsers.Primitives;
namespace CanonSharp.Combinator;
public static class ParserBuilder
{
#region PrimitiveParser
// 对应Parsers.Primitives命名空间下的Parser实现
/// <summary>
/// 直接成功的解析器
/// </summary>
/// <param name="value"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Pure<TToken, T>(T value)
=> new PureParser<TToken, T>(value);
/// <summary>
/// 直接成功的解析器
/// </summary>
/// <param name="valueFunc">生成结果的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Pure<TToken, T>(Func<IReadState<TToken>, T> valueFunc)
=> new DelayedPureParser<TToken, T>(valueFunc);
/// <summary>
/// 生成空结果的解析器
/// </summary>
/// <typeparam name="TToken"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, Unit> Null<TToken>() => Pure<TToken, Unit>(Unit.Instance);
/// <summary>
/// 失败的解析器
/// </summary>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Fail<TToken, T>() => new FailedParser<TToken, T>();
/// <summary>
/// 失败的解析器
/// </summary>
/// <param name="message">失败的原因</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Fail<TToken, T>(string message) => new FailedParserWithMessage<TToken, T>(message);
/// <summary>
/// 失败的解析器
/// </summary>
/// <param name="messageFunc">产生失败原因的函数</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Fail<TToken, T>(Func<IReadState<TToken>, string> messageFunc) =>
new FailedParserWithDelayedMessage<TToken, T>(messageFunc);
/// <summary>
/// 失败的解析器
/// </summary>
/// <param name="exception">失败的异常</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Fail<TToken, T>(Exception exception) =>
new FailedParserWithException<TToken, T>(exception);
/// <summary>
/// 满足指定条件的解析器
/// </summary>
/// <param name="predicate"></param>
/// <typeparam name="TToken"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TToken> Satisfy<TToken>(Func<TToken, bool> predicate)
=> new SatisfyParser<TToken>(predicate);
/// <summary>
/// 识别任何输入的解析器
/// </summary>
/// <typeparam name="TToken"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TToken> Any<TToken>() => Satisfy<TToken>(_ => true);
/// <summary>
/// 识别指定输入元素的解析器
/// </summary>
/// <param name="token">识别的指定元素</param>
/// <typeparam name="TToken"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, TToken> Token<TToken>(TToken token)
=> Satisfy<TToken>(t => EqualityComparer<TToken>.Default.Equals(t, token));
/// <summary>
/// 跳过指定数量输入元素的解析器
/// </summary>
/// <param name="count"></param>
/// <typeparam name="TToken"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, Unit> Skip<TToken>(int count) => new SkipParser<TToken>(count);
/// <summary>
/// 识别指定数量输入元素的解析器
/// </summary>
/// <param name="count"></param>
/// <typeparam name="TToken"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<TToken>> Take<TToken>(int count) => new TakeParser<TToken>(count);
#endregion
#region Bases
/// <summary>
/// 按照给定的函数修改解析器的解析器
/// </summary>
/// <param name="parserFix"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Fix<TToken, T>(Func<Parser<TToken, T>, Parser<TToken, T>> parserFix)
=> new FixParser<TToken, T>(parserFix);
#endregion
#region Combinators
/// <summary>
/// 按照给定的解析器组依次尝试
/// </summary>
/// <param name="parsers"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Choice<TToken, T>(IEnumerable<Parser<TToken, T>> parsers)
=> parsers.Reverse().Aggregate((next, parser) => parser.Alternative(next));
/// <summary>
/// 按照给定的解析器组依次尝试
/// </summary>
/// <param name="parsers"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, T> Choice<TToken, T>(params Parser<TToken, T>[] parsers)
=> Choice(parsers.AsEnumerable());
/// <summary>
/// 顺序应用所有输入的解析器
/// </summary>
/// <param name="parsers"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Sequence<TToken, T>(IEnumerable<Parser<TToken, T>> parsers)
=> parsers.Reverse().Aggregate(Pure<TToken, IEnumerable<T>>([]),
(next, parser) => parser.Bind(
x => next.Map(result => result.Prepend(x))));
/// <summary>
/// 顺序应用输入输入的解析器
/// </summary>
/// <param name="parsers"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<T>> Sequence<TToken, T>(params Parser<TToken, T>[] parsers)
=> Sequence(parsers.AsEnumerable());
/// <summary>
/// 识别输入令牌直到终止解析器运行成功
/// 在终止解析器之前可以存在零个或者多个输入令牌
/// </summary>
/// <param name="terminator"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<TToken>> TakeTill<TToken, TIgnore>(Parser<TToken, TIgnore> terminator)
=> Any<TToken>().ManyTill(terminator);
/// <summary>
/// 识别输入令牌直到终止解析器运行成功
/// 在终止解析器之前至少存在一个输入令牌
/// </summary>
/// <param name="termintor"></param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIgnore"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<TToken, IEnumerable<TToken>> Take1Till<TToken, TIgnore>(Parser<TToken, TIgnore> termintor)
=> Any<TToken>().Many1Till(termintor);
#endregion
}

View File

@ -0,0 +1,21 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Bases;
/// <summary>
/// 选择解析器
/// 如果第一个不成功则调用第二个
/// </summary>
/// <param name="first">第一个解析器</param>
/// <param name="second">第二个解析器</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析器结果类型</typeparam>
internal sealed class AlternativeParser<TToken, T>(Parser<TToken, T> first, Parser<TToken, T> second)
: Parser<TToken, T>
{
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
{
return first.Run(state, result => result.CaseOf(continuation, _ => second.Run(state, continuation)));
}
}

View File

@ -0,0 +1,20 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Bases;
/// <summary>
/// 单子解析器
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="next">下游解析器生成函数</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TIntermediate">上游解析器结果类型</typeparam>
/// <typeparam name="T">下游解析器结果类型</typeparam>
internal sealed class BindParser<TToken, TIntermediate, T>(
Parser<TToken, TIntermediate> parser,
Func<TIntermediate, Parser<TToken, T>> next) : Parser<TToken, T>
{
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
=> parser.Run(state, result => result.Next(next, continuation));
}

View File

@ -0,0 +1,33 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Bases;
/// <summary>
/// 修正?解析器
/// 感觉是一种递归的高级实现?
///
/// </summary>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="T"></typeparam>
internal sealed class FixParser<TToken, T> : Parser<TToken, T>
{
private readonly Parser<TToken, T> _parser;
public FixParser(Func<Parser<TToken, T>, Parser<TToken, T>> func)
{
_parser = func(this);
}
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
=> _parser.Run(state, continuation);
}
internal sealed class FixParser<TToken, TParameter, T>(
Func<Func<TParameter, Parser<TToken, T>>, TParameter, Parser<TToken, T>> func,
TParameter parameter) : Parser<TToken, T>
{
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
=> func(p => new FixParser<TToken, TParameter, T>(func, p), parameter).Run(state, continuation);
}

View File

@ -0,0 +1,21 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Bases;
/// <summary>
/// 映射解析器
/// 提供一个函数修改上游解析器返回的结果
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="func">修改上游解析器返回结果的</param>
/// <typeparam name="TToken"></typeparam>
/// <typeparam name="TIntermediate"></typeparam>
/// <typeparam name="T"></typeparam>
internal sealed class MapParser<TToken, TIntermediate, T>(
Parser<TToken, TIntermediate> parser,
Func<TIntermediate, T> func) : Parser<TToken, T>
{
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
=> parser.Run(state, result => continuation(result.Map(func)));
}

View File

@ -0,0 +1,26 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Bases;
/// <summary>
/// 下一步解析器
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="successfulParser">成功情况下的解析器函数</param>
/// <param name="failedParser">失败情况下的解析器函数</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TIntermediate">上游解析器结果类型</typeparam>
/// <typeparam name="T">最终解析结果类型</typeparam>
internal sealed class NextParser<TToken, TIntermediate, T>(
Parser<TToken, TIntermediate> parser,
Func<TIntermediate, Parser<TToken, T>> successfulParser,
Func<FailedResult<TToken, TIntermediate>, Parser<TToken, T>> failedParser) : Parser<TToken, T>
{
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
{
return parser.Run(state, result => result.CaseOf(
successfulResult => successfulResult.Next(successfulParser, continuation),
failedResult => failedParser(failedResult).Run(state, continuation)));
}
}

View File

@ -0,0 +1,24 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Bases;
/// <summary>
/// 恢复解析器
/// 在上游解析器失败的情况下调用指定恢复函数返回的解析器
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="failedHandler">返回新解析器的恢复函数</param>
/// <typeparam name="TToken">输入令牌类型</typeparam>
/// <typeparam name="T">解析结果类型</typeparam>
internal sealed class ResumeParser<TToken, T>(
Parser<TToken, T> parser,
Func<FailedResult<TToken, T>, Parser<TToken, T>> failedHandler) : Parser<TToken, T>
{
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
{
return parser.Run(state,
result => result.CaseOf(continuation,
failedResult => failedHandler(failedResult).Run(state, continuation)));
}
}

View File

@ -0,0 +1,27 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers;
/// <summary>
/// 修改解析器返回结果的解析器基类
/// </summary>
/// <param name="parser">需要修改结果的解析器</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TIntermediate">需要修改结果的解析器</typeparam>
/// <typeparam name="T">最终返回的解析结果</typeparam>
public abstract class ModifiedParser<TToken, TIntermediate, T>(Parser<TToken, TIntermediate> parser) : Parser<TToken, T>
{
protected abstract ParseResult<TToken, T> Fail<TState>(TState state,
FailedResult<TToken, TIntermediate> failedResult)
where TState : IReadState<TToken, TState>;
protected abstract ParseResult<TToken, T> Succeed<TState>(TState state,
SuccessfulResult<TToken, TIntermediate> successfulResult)
where TState : IReadState<TToken, TState>;
internal override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
=> parser.Run(state, result => result.CaseOf(
success => continuation(Succeed(state, success)),
failure => continuation(Fail(state, failure))));
}

View File

@ -0,0 +1,30 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Modifiers;
/// <summary>
/// 对结果运行指定操作,但是不做修改操作的解析器
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="succeed">对成功结果的操作</param>
/// <param name="fail">对失败结果的操作</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果类型</typeparam>
internal sealed class DoParser<TToken, T>(
Parser<TToken, T> parser,
Action<T> succeed,
Action<FailedResult<TToken, T>> fail) : ModifiedParser<TToken, T, T>(parser)
{
protected override ParseResult<TToken, T> Succeed<TState>(TState state,
SuccessfulResult<TToken, T> successfulResult)
{
succeed(successfulResult.Value);
return successfulResult;
}
protected override ParseResult<TToken, T> Fail<TState>(TState state, FailedResult<TToken, T> failedResult)
{
fail(failedResult);
return failedResult;
}
}

View File

@ -0,0 +1,21 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Modifiers;
/// <summary>
/// 向前看解析器
/// 使用传入的解析器向前解析
/// 但是返回的结果中输入流读取状态不前移
/// </summary>
/// <param name="parser">需要向前看的解析器</param>
/// <typeparam name="TToken">输入流令牌</typeparam>
/// <typeparam name="T">返回的解析结果类型</typeparam>
internal sealed class LookAheadParser<TToken, T>(Parser<TToken, T> parser) : ModifiedParser<TToken, T, T>(parser)
{
protected override ParseResult<TToken, T> Succeed<TState>(TState state,
SuccessfulResult<TToken, T> successfulResult)
=> ParseResultBuilder.Succeed<TToken, TState, T>(successfulResult.Value, state);
protected override ParseResult<TToken, T> Fail<TState>(TState state, FailedResult<TToken, T> failedResult)
=> ParseResultBuilder.Fail<TToken, TState, T>($"Failed when looking ahead: {failedResult}", state);
}

View File

@ -0,0 +1,26 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Modifiers;
/// <summary>
/// 翻转结果的解析器
/// 当成功时失败
/// 当失败时返回指定的成功结果
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="result">期望中的结果</param>
/// <typeparam name="TToken">输入流的类型</typeparam>
/// <typeparam name="TIntermediate">上游解析器结果类型</typeparam>
/// <typeparam name="T">最终的返回结果</typeparam>
internal sealed class ReverseParser<TToken, TIntermediate, T>(Parser<TToken, TIntermediate> parser, T result)
: ModifiedParser<TToken, TIntermediate, T>(parser)
{
protected override ParseResult<TToken, T> Succeed<TState>(TState state,
SuccessfulResult<TToken, TIntermediate> successfulResult)
=> ParseResultBuilder.Fail<TToken, TState, T>($"Unexpected successful result: {successfulResult.Value}",
state);
protected override ParseResult<TToken, T> Fail<TState>(TState state,
FailedResult<TToken, TIntermediate> failedResult)
=> ParseResultBuilder.Succeed<TToken, TState, T>(result, state);
}

View File

@ -0,0 +1,26 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Modifiers;
/// <summary>
/// 成功映射的解析器
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="successfulHandler">当上游成功时的处理函数</param>
/// <param name="failedHandler">当上游失败时的处理函数</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TIntermediate">上游解析器解析结果类型</typeparam>
/// <typeparam name="T">最终的解析结果类型</typeparam>
internal sealed class SuccessfulMapParser<TToken, TIntermediate, T>(
Parser<TToken, TIntermediate> parser,
Func<TIntermediate, T> successfulHandler,
Func<FailedResult<TToken, TIntermediate>, T> failedHandler) : ModifiedParser<TToken, TIntermediate, T>(parser)
{
protected override ParseResult<TToken, T> Succeed<TState>(TState state,
SuccessfulResult<TToken, TIntermediate> successfulResult)
=> successfulResult.Map(successfulHandler);
protected override ParseResult<TToken, T> Fail<TState>(TState state,
FailedResult<TToken, TIntermediate> failedResult)
=> ParseResultBuilder.Succeed<TToken, TState, T>(failedHandler(failedResult), state);
}

View File

@ -0,0 +1,23 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Modifiers;
/// <summary>
/// 尝试的解析器
/// 当成功时直接返回原结果
/// 当失败时调用resume函数处理失败结果并返回成功结果
/// </summary>
/// <param name="parser">上游解析器</param>
/// <param name="resume">处理失败结果的恢复函数</param>
/// <typeparam name="TToken">输入流令牌</typeparam>
/// <typeparam name="T">解析器返回结果类型</typeparam>
internal sealed class TryParser<TToken, T>(Parser<TToken, T> parser, Func<FailedResult<TToken, T>, T> resume)
: ModifiedParser<TToken, T, T>(parser)
{
protected override ParseResult<TToken, T> Succeed<TState>(TState state,
SuccessfulResult<TToken, T> successfulResult)
=> successfulResult;
protected override ParseResult<TToken, T> Fail<TState>(TState state, FailedResult<TToken, T> failedResult)
=> ParseResultBuilder.Succeed<TToken, TState, T>(resume(failedResult), state);
}

View File

@ -0,0 +1,25 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers;
/// <summary>
/// 解析器原型基类
/// 实际上就是处理了一个后继调用
/// </summary>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
public abstract class PrimitiveParser<TToken, T> : Parser<TToken, T>
{
/// <summary>
/// 运行解析器 返回解析结果
/// </summary>
/// <param name="state">当前输入流的状态</param>
/// <typeparam name="TState">输入流状态的类型</typeparam>
/// <returns>解析结果</returns>
protected abstract ParseResult<TToken, T> Run<TState>(TState state)
where TState : IReadState<TToken, TState>;
internal sealed override ParseResult<TToken, TResult> Run<TState, TResult>(TState state,
Func<ParseResult<TToken, T>, ParseResult<TToken, TResult>> continuation)
=> continuation(Run(state));
}

View File

@ -0,0 +1,51 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Primitives;
/// <summary>
/// 直接失败的解析器
/// </summary>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
internal sealed class FailedParser<TToken, T> : PrimitiveParser<TToken, T>
{
protected override ParseResult<TToken, T> Run<TState>(TState state)
=> ParseResultBuilder.Fail<TToken, TState, T>(state);
}
/// <summary>
/// 含有失败信息的失败解析器
/// </summary>
/// <param name="message">失败信息</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
internal sealed class FailedParserWithMessage<TToken, T>(string message) : PrimitiveParser<TToken, T>
{
protected override ParseResult<TToken, T> Run<TState>(TState state)
=> ParseResultBuilder.Fail<TToken, TState, T>(message, state);
}
/// <summary>
/// 按照输入状态产生失败信息的失败解析器
/// </summary>
/// <param name="messageFunc">产生失败信息的函数</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
internal sealed class FailedParserWithDelayedMessage<TToken, T>(Func<IReadState<TToken>, string> messageFunc)
: PrimitiveParser<TToken, T>
{
protected override ParseResult<TToken, T> Run<TState>(TState state)
=> ParseResultBuilder.Fail<TToken, TState, T>(messageFunc(state), state);
}
/// <summary>
/// 含有失败异常的失败解析器
/// </summary>
/// <param name="e">异常</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
internal sealed class FailedParserWithException<TToken, T>(Exception e) : PrimitiveParser<TToken, T>
{
protected override ParseResult<TToken, T> Run<TState>(TState state)
=> ParseResultBuilder.Fail<TToken, TState, T>(e, state);
}

View File

@ -0,0 +1,27 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Primitives;
/// <summary>
/// 直接成功的解析器
/// </summary>
/// <param name="value">解析成功返回的值</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析成功返回值的类型</typeparam>
internal sealed class PureParser<TToken, T>(T value) : PrimitiveParser<TToken, T>
{
protected override ParseResult<TToken, T> Run<TState>(TState state)
=> ParseResultBuilder.Succeed<TToken, TState, T>(value, state);
}
/// <summary>
/// 按照输入状态返回结果的始终成功解析器
/// </summary>
/// <param name="valueFunc">按照输入状态返回解析结果的函数</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="T">解析成功返回值的类型</typeparam>
internal sealed class DelayedPureParser<TToken, T>(Func<IReadState<TToken>, T> valueFunc) : PrimitiveParser<TToken, T>
{
protected override ParseResult<TToken, T> Run<TState>(TState state)
=> ParseResultBuilder.Succeed<TToken, TState, T>(valueFunc(state), state);
}

View File

@ -0,0 +1,18 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Parsers.Primitives;
/// <summary>
/// 满足指定条件即成功的解析器
/// </summary>
/// <param name="predicate">满足的条件谓词</param>
/// <typeparam name="TToken">输入流类型</typeparam>
internal sealed class SatisfyParser<TToken>(Func<TToken, bool> predicate) : PrimitiveParser<TToken, TToken>
{
protected override ParseResult<TToken, TToken> Run<TState>(TState state)
{
return state.HasValue && predicate(state.Current)
? ParseResultBuilder.Succeed<TToken, TState, TToken>(state.Current, state.Next)
: ParseResultBuilder.Fail<TToken, TState, TToken>(state);
}
}

View File

@ -0,0 +1,22 @@
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
namespace CanonSharp.Combinator.Parsers.Primitives;
/// <summary>
/// 跳过指定数量的输入令牌
/// </summary>
/// <param name="count">需要跳过的令牌数量</param>
/// <typeparam name="TToken">输入流类型</typeparam>
internal sealed class SkipParser<TToken>(int count) : PrimitiveParser<TToken, Unit>
{
protected override ParseResult<TToken, Unit> Run<TState>(TState state)
{
List<TState> result = state.AsEnumerable<TToken, TState>().Take(count).ToList();
return result.Count == count
? ParseResultBuilder.Succeed<TToken, TState, Unit>(Unit.Instance,
result.Count == 0 ? state : result.Last().Next)
: ParseResultBuilder.Fail<TToken, TState, Unit>("An input does not have required length.", state);
}
}

View File

@ -0,0 +1,23 @@
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
namespace CanonSharp.Combinator.Parsers.Primitives;
/// <summary>
/// 解析指定数量的解析器
/// </summary>
/// <param name="count">需要解析的数量</param>
/// <typeparam name="TToken">输入流类型</typeparam>
internal sealed class TakeParser<TToken>(int count) : PrimitiveParser<TToken, IEnumerable<TToken>>
{
protected override ParseResult<TToken, IEnumerable<TToken>> Run<TState>(TState state)
{
List<TState> result = state.AsEnumerable<TToken, TState>().Take(count).ToList();
return result.Count == count
? ParseResultBuilder.Succeed<TToken, TState, IEnumerable<TToken>>(result.Select(s => s.Current),
result.Count == 0 ? state : result.Last().Next)
: ParseResultBuilder.Fail<TToken, TState, IEnumerable<TToken>>("An input does not have required length.",
state);
}
}

View File

@ -0,0 +1,22 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Results;
/// <summary>
/// 错误类型的失败解析结果
/// </summary>
/// <typeparam name="TToken">输入流的类型</typeparam>
/// <typeparam name="TState">输入流的读取类型</typeparam>
/// <typeparam name="T">实际的结果类型</typeparam>
internal sealed class FailedResultWithError<TToken, TState, T>(TState state) : FailedResult<TToken, T>
where TState : IReadState<TToken, TState>
{
public override IReadState<TToken> State => state;
public override string Message => $"Unexpected state: {state}.";
public override FailedResult<TToken, TNext> Convert<TNext>()
{
return new FailedResultWithError<TToken, TState, TNext>(state);
}
}

View File

@ -0,0 +1,24 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Results;
/// <summary>
/// 异常类型的失败解析结果
/// </summary>
/// <param name="exception">解析中发生的异常</param>
/// <param name="state">当前输入流的状态</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TState">当前输入流状态的类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
public class FailedResultWithException<TToken, TState, T>(Exception exception, TState state) : FailedResult<TToken, T>
where TState : IReadState<TToken, TState>
{
public override IReadState<TToken> State => state;
public override ParseException Exception => new(ToString(), exception);
public override string Message => $"Exception occured: {exception}.";
public override FailedResult<TToken, TNext> Convert<TNext>()
=> new FailedResultWithException<TToken, TState, TNext>(exception, state);
}

View File

@ -0,0 +1,24 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Results;
/// <summary>
/// 消息类型的失败解析结果
/// </summary>
/// <param name="message">解析失败的消息</param>
/// <param name="state">当前读取的状态</param>
/// <typeparam name="TToken">输入流的类型</typeparam>
/// <typeparam name="TState">读取状态类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
internal sealed class FailedResultWithMessage<TToken, TState, T>(string message, TState state) : FailedResult<TToken, T>
where TState : IReadState<TToken, TState>
{
public override IReadState<TToken> State => state;
public override string Message => message;
public override FailedResult<TToken, TNext> Convert<TNext>()
{
return new FailedResultWithMessage<TToken, TState, TNext>(message, state);
}
}

View File

@ -0,0 +1,23 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Combinator.Results;
/// <summary>
/// 实际实现的解析成功结果
/// </summary>
/// <param name="result">解析结果</param>
/// <param name="state">解析成功之后的下一个输入流状态</param>
/// <typeparam name="TToken">输入流类型</typeparam>
/// <typeparam name="TState">输入流的状态类型</typeparam>
/// <typeparam name="T">解析结果的类型</typeparam>
internal sealed class InternalSuccessfulResult<TToken, TState, T>(T result, TState state)
: SuccessfulResult<TToken, T>(result)
where TState : IReadState<TToken, TState>
{
protected override ParseResult<TToken, TResult> RunNext<TNext, TResult>(Parser<TToken, TNext> parser,
Func<ParseResult<TToken, TNext>, ParseResult<TToken, TResult>> continuation)
=> parser.Run(state, continuation);
public override ParseResult<TToken, TResult> Map<TResult>(Func<T, TResult> map)
=> new InternalSuccessfulResult<TToken, TState, TResult>(map(Value), state);
}

View File

@ -0,0 +1,22 @@
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Combinator.Parsers;
namespace CanonSharp.Combinator.Text;
/// <summary>
/// 字符串解析器
/// </summary>
/// <param name="except">期望的字符串</param>
/// <param name="comparison">字符串比较模式</param>
public class StringParser(string except, StringComparison comparison) : PrimitiveParser<char, string>
{
protected override ParseResult<char, string> Run<TState>(TState state)
{
TState[] states = state.AsEnumerable<char, TState>().Take(except.Length).ToArray();
string actual = new(states.Select(x => x.Current).ToArray());
return string.Equals(except, actual, comparison)
? ParseResultBuilder.Succeed<char, TState, string>(actual, states.Length == 0 ? state : states.Last().Next)
: ParseResultBuilder.Fail<char, TState, string>($"Except '{except}' but found '{actual}.", state);
}
}

View File

@ -0,0 +1,123 @@
using System.Runtime.CompilerServices;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using static CanonSharp.Combinator.ParserBuilder;
namespace CanonSharp.Combinator.Text;
public static class TextParserBuilder
{
/// <summary>
/// 识别单个字符
/// </summary>
/// <param name="token">识别的单个字符</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> Char(char token) => Satisfy<char>(x => x == token);
/// <summary>
/// 忽略大小写识别单个字符
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> CharIgnoreCase(char token) =>
Satisfy<char>(x => char.ToUpperInvariant(x) == char.ToUpperInvariant(token));
/// <summary>
/// 识别提供字符串中的一个字符
/// </summary>
/// <param name="candidate"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> OneOf(string candidate) => Satisfy<char>(candidate.Contains);
/// <summary>
/// 忽略大小写识别字符串中的一个字符
/// </summary>
/// <param name="candidate"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> OneOfIgnoreCase(string candidate) =>
Satisfy<char>(x => candidate.Contains(x, StringComparison.OrdinalIgnoreCase));
/// <summary>
/// 识别一个字符串
/// </summary>
/// <param name="except">识别的字符串</param>
/// <param name="comparison">字符串比较方法</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, string> String(string except, StringComparison comparison) =>
new StringParser(except, comparison);
/// <summary>
/// 识别一个字符串
/// </summary>
/// <param name="except"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, string> String(string except) => String(except, StringComparison.Ordinal);
/// <summary>
/// 忽略大小写识别一个字符串
/// </summary>
/// <param name="except"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, string> StringIgnoreCase(string except) =>
String(except, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// 识别范围内的所有字符
/// </summary>
/// <param name="start">包括的起始字符</param>
/// <param name="end">包括的终止字符</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> Range(char start, char end) => Satisfy<char>(x => x >= start && x <= end);
/// <summary>
/// 识别Unicode字符类别的解析器
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> Letter() => Satisfy<char>(char.IsLetter);
/// <summary>
/// 识别Unicode数字类别的解析器
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> Digit() => Satisfy<char>(char.IsDigit);
/// <summary>
/// 识别ASCII字符类别的解析器
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> AsciiLetter() =>
Satisfy<char>(x => x is >= 'a' and <= 'z' or >= 'A' and <= 'Z');
/// <summary>
/// 识别ASCII数字类别的解析器
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> AsciiDigit() => Satisfy<char>(x => x is >= '0' and <= '9');
/// <summary>
/// 识别Unicode空白类型的字符
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, char> Space() => Satisfy<char>(char.IsWhiteSpace);
/// <summary>
/// 识别所有的换行符
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Parser<char, string> LineBreak() =>
OneOf("\u000D\u000A\u0085\u2028\u2029\n").Map(x => x.ToString()) | String("\r\n");
}

View File

@ -0,0 +1,14 @@
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using static CanonSharp.Combinator.Text.TextParserBuilder;
namespace CanonSharp.Combinator.Text;
public static class TextParserExtensions
{
public static Parser<char, T> SkipSpaces<T>(this Parser<char, T> parser)
=> Space().SkipTill(parser);
public static Parser<char, T> SkipSpaceAndLineBreak<T>(this Parser<char, T> parser)
=> (Space().Map(x => x.ToString()) | LineBreak()).SkipTill(parser);
}

View File

@ -0,0 +1,23 @@
namespace CanonSharp.Combinator;
/// <summary>
/// 单元类型Unit
/// </summary>
public readonly struct Unit : IComparable<Unit>, IEquatable<Unit>
{
public static Unit Instance => default;
public bool Equals(Unit other) => true;
public int CompareTo(Unit other) => 0;
public override bool Equals(object? obj) => obj is Unit;
public override int GetHashCode() => 0;
public override string ToString() => $"<{nameof(Unit)}>";
public static bool operator ==(Unit _0, Unit _1) => true;
public static bool operator !=(Unit _0, Unit _1) => false;
}

View File

@ -1,17 +0,0 @@
namespace CanonSharp.Common.Abstractions;
public interface ISourceReader
{
/// <summary>
/// 偷看一下下一个字符
/// </summary>
/// <param name="c">看到的下一个字符</param>
/// <returns></returns>
public bool TryPeek(out char c);
/// <summary>
/// 读取下一个字符
/// </summary>
/// <returns></returns>
public char Read();
}

View File

@ -11,4 +11,8 @@
<None Include="../.editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CanonSharp.Combinator\CanonSharp.Combinator.csproj" />
</ItemGroup>
</Project>

View File

@ -1,111 +0,0 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public class DeterministicState : IEquatable<DeterministicState>
{
public Guid Id { get; }
public Dictionary<char, DeterministicState> Transaction { get; } = [];
public HashSet<NondeterministicState> Closure { get; }
public DeterministicState(HashSet<NondeterministicState> closure)
{
Id = Guid.NewGuid();
Closure = closure;
}
private DeterministicState(DeterministicState state)
{
Id = state.Id;
Transaction = state.Transaction;
Closure = [];
}
public DeterministicState StripClosure() => new(this);
public bool Equals(DeterministicState? other) => other is not null && Id.Equals(other.Id);
public override bool Equals(object? obj) => obj is DeterministicState other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
}
public class DeterministicFiniteAutomation
{
public DeterministicState Start { get; }
public HashSet<DeterministicState> FinalStates { get; }
private DeterministicFiniteAutomation(DeterministicState start, HashSet<DeterministicState> finalStates)
{
Start = start;
FinalStates = finalStates;
}
private record Pair(HashSet<NondeterministicState> States, DeterministicState State);
public static DeterministicFiniteAutomation Create(NondeterministicFiniteAutomation nfa)
{
Dictionary<NondeterministicStateSet, DeterministicState> map = [];
HashSet<DeterministicState> visited = [];
Queue<Pair> queue = [];
HashSet<DeterministicState> finalStates = [];
HashSet<NondeterministicState> startClosure = nfa.Start.CalculateEmptyClosure();
DeterministicState start = new(startClosure);
map.Add(new NondeterministicStateSet(startClosure), start);
queue.Enqueue(new Pair(startClosure, start));
while (queue.TryDequeue(out Pair? pair))
{
if (pair.States.Any(s => nfa.FinalStates.Contains(s)))
{
finalStates.Add(pair.State);
}
Dictionary<char, HashSet<NondeterministicState>> next = [];
foreach (NondeterministicState state in pair.States)
{
foreach (KeyValuePair<EmptyChar, HashSet<NondeterministicState>> transaction in
state.Transactions.Where(p => !p.Key.IsEmpty))
{
HashSet<NondeterministicState> closure = [];
foreach (NondeterministicState s in transaction.Value)
{
closure.UnionWith(s.CalculateEmptyClosure());
}
if (next.TryGetValue(transaction.Key.Char, out HashSet<NondeterministicState>? n))
{
n.UnionWith(closure);
}
else
{
next.Add(transaction.Key.Char, closure);
}
}
}
foreach (KeyValuePair<char, HashSet<NondeterministicState>> transaction in next)
{
NondeterministicStateSet set = new(transaction.Value);
if (!map.TryGetValue(set, out DeterministicState? nextState))
{
nextState = new DeterministicState(transaction.Value);
map.Add(set, nextState);
}
pair.State.Transaction.Add(transaction.Key, nextState);
if (visited.Add(nextState))
{
queue.Enqueue(new Pair(transaction.Value, nextState));
}
}
}
return new DeterministicFiniteAutomation(start, finalStates);
}
}

View File

@ -1,49 +0,0 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public class EmptyChar : IEquatable<EmptyChar>
{
public bool IsEmpty { get; }
public char Char { get; }
public static EmptyChar Empty => new();
private EmptyChar()
{
IsEmpty = true;
Char = char.MaxValue;
}
public EmptyChar(char c)
{
IsEmpty = false;
Char = c;
}
public bool Equals(EmptyChar? other)
{
if (other is null)
{
return false;
}
if (IsEmpty)
{
return other.IsEmpty;
}
return Char == other.Char;
}
public override bool Equals(object? obj) => obj is EmptyChar other && Equals(other);
public override int GetHashCode()
{
return IsEmpty.GetHashCode() ^ Char.GetHashCode();
}
public override string ToString()
{
return IsEmpty ? "ε" : Char.ToString();
}
}

View File

@ -1,92 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using CanonSharp.Common.Abstractions;
namespace CanonSharp.Common.LexicalAnalyzer;
public class LexicalScanner(
DeterministicState startState,
Dictionary<DeterministicState, LexicalToken> finalStateMap,
HashSet<LexicalToken> skippedTokens,
ISourceReader reader)
{
private readonly DeterministicState _startState = startState;
private readonly List<char> _readHistory = [];
private DeterministicState _currentState = startState;
public bool TryRead([NotNullWhen(true)] out LexicalToken? token)
{
while (TryReadInternal(out token))
{
if (!skippedTokens.Contains(token))
{
return true;
}
}
return false;
}
private bool TryReadInternal([NotNullWhen(true)] out LexicalToken? token)
{
while (reader.TryPeek(out char c))
{
if (_currentState.Transaction.TryGetValue(c, out DeterministicState? nextState))
{
// 可以迁移到下一个状态
_currentState = nextState;
_readHistory.Add(reader.Read());
}
else
{
// 无法迁移到下一个状态
if (!finalStateMap.TryGetValue(_currentState, out LexicalToken? possibleToken))
{
throw new InvalidOperationException();
}
// 当前状态是终止状态
token = new LexicalToken(possibleToken, new string(_readHistory.ToArray()));
// 重置状态
_readHistory.Clear();
_currentState = _startState;
return true;
}
}
// 当前状态是终止状态
if (finalStateMap.TryGetValue(_currentState, out LexicalToken? possibleToken2))
{
token = new LexicalToken(possibleToken2, new string(_readHistory.ToArray()));
_readHistory.Clear();
_currentState = _startState;
return true;
}
if (!_currentState.Equals(_startState))
{
throw new InvalidOperationException();
}
token = null;
return false;
}
public static LexicalScannerBuilder CreateDefaultBuilder()
{
LexicalScannerBuilder builder = new();
builder.DefineToken(LexicalToken.LineBreaker);
builder.DefineToken(LexicalToken.WhiteSpace);
builder.AddSkippedToken(LexicalToken.LineBreaker);
builder.AddSkippedToken(LexicalToken.WhiteSpace);
return builder;
}
public static LexicalScannerBuilder CreateEmptyBuilder() => new();
}

View File

@ -1,113 +0,0 @@
using CanonSharp.Common.Abstractions;
namespace CanonSharp.Common.LexicalAnalyzer;
public class LexicalScannerBuilder
{
private readonly Dictionary<NondeterministicState, LexicalToken> _finalStateMap = [];
private readonly List<NondeterministicFiniteAutomation> _nondeterministicFiniteAutomations = [];
private readonly HashSet<LexicalToken> _skippedTokens = [];
internal LexicalScannerBuilder()
{
}
public void DefineToken(LexicalToken token)
{
NondeterministicFiniteAutomation automation = token.Expression.Convert2Nfa();
_nondeterministicFiniteAutomations.Add(automation);
foreach (NondeterministicState state in automation.FinalStates)
{
_finalStateMap.Add(state, token);
}
}
/// <summary>
/// 定义词法令牌
/// </summary>
/// <param name="expression">该令牌的正则表达式</param>
/// <param name="priority">识别该令牌的优先级</param>
/// <returns>定义好的词法令牌</returns>
public LexicalToken DefineToken(RegularExpression expression, int priority)
{
LexicalToken token = new(expression, priority);
DefineToken(token);
return token;
}
/// <summary>
/// 定义输出时需要跳过的词法令牌
/// </summary>
/// <param name="expression">该令牌的正则表达式</param>
/// <param name="priority">该令牌的优先级</param>
public void DefineSkippedToken(RegularExpression expression, int priority)
{
LexicalToken token = DefineToken(expression, priority);
AddSkippedToken(token);
}
public void AddSkippedToken(LexicalToken token) => _skippedTokens.Add(token);
public LexicalScanner Build(ISourceReader reader)
{
NondeterministicFiniteAutomation finaAutomation = Combine();
DeterministicFiniteAutomation deterministicFiniteAutomation =
DeterministicFiniteAutomation.Create(finaAutomation);
Dictionary<DeterministicState, LexicalToken> finalTokenMap = [];
foreach (DeterministicState state in deterministicFiniteAutomation.FinalStates)
{
finalTokenMap.Add(state.StripClosure(), state.Closure
.Where(s => _finalStateMap.ContainsKey(s))
.Select(s => _finalStateMap[s])
.OrderByDescending(t => t.Priority)
.First());
}
// 清除在分析中不需要的Closure引用
// 释放内存占用
Queue<DeterministicState> queue = [];
HashSet<DeterministicState> visited = [deterministicFiniteAutomation.Start];
DeterministicState strippedStartState = deterministicFiniteAutomation.Start.StripClosure();
queue.Enqueue(strippedStartState);
while (queue.TryDequeue(out DeterministicState? state))
{
Dictionary<char, DeterministicState> transactions = [];
foreach (KeyValuePair<char,DeterministicState> pair in state.Transaction)
{
transactions.Add(pair.Key, pair.Value.StripClosure());
}
state.Transaction.Clear();
foreach (KeyValuePair<char,DeterministicState> pair in transactions)
{
state.Transaction.Add(pair.Key, pair.Value);
if (visited.Add(pair.Value))
{
queue.Enqueue(pair.Value);
}
}
}
return new LexicalScanner(strippedStartState, finalTokenMap, _skippedTokens, reader);
}
private NondeterministicFiniteAutomation Combine()
{
NondeterministicState head = new();
NondeterministicFiniteAutomation result = new(head, []);
foreach (NondeterministicFiniteAutomation automation in _nondeterministicFiniteAutomations)
{
head.AddTransaction(EmptyChar.Empty, automation.Start);
result.FinalStates.UnionWith(automation.FinalStates);
}
return result;
}
}

View File

@ -1,54 +0,0 @@
using System.Globalization;
namespace CanonSharp.Common.LexicalAnalyzer;
public class LexicalToken : IEquatable<LexicalToken>
{
private readonly Guid _tokenId;
public RegularExpression Expression { get; }
public int Priority { get; }
public LexicalToken(RegularExpression expression, int priority)
{
_tokenId = Guid.NewGuid();
Expression = expression;
Priority = priority;
LiteralValue = string.Empty;
}
internal LexicalToken(LexicalToken definition, string literalValue)
{
_tokenId = definition._tokenId;
Expression = definition.Expression;
Priority = definition.Priority;
LiteralValue = literalValue;
}
public string LiteralValue { get; }
public bool Equals(LexicalToken? other) => other is not null && _tokenId == other._tokenId;
public override bool Equals(object? obj) => obj is LexicalToken other && Equals(other);
public override int GetHashCode() => _tokenId.GetHashCode();
public static bool operator ==(LexicalToken a, LexicalToken b) => a.Equals(b);
public static bool operator !=(LexicalToken a, LexicalToken b) => !(a == b);
/// <summary>
/// 匹配所有的空白字符
/// </summary>
public static readonly LexicalToken WhiteSpace = new(
RegularExpression.CharSetOf(c => char.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator) |
RegularExpression.CharSetOf("\u0009\u000B\u000C"), int.MinValue);
/// <summary>
/// 匹配所有的换行符
/// </summary>
public static readonly LexicalToken LineBreaker = new(
RegularExpression.CharSetOf("\u000D\u000A\u0085\u2028\u2029") |
RegularExpression.String("\r\n"), int.MinValue);
}

View File

@ -1,76 +0,0 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public class NondeterministicState : IEquatable<NondeterministicState>
{
public Guid Id { get; } = Guid.NewGuid();
public Dictionary<EmptyChar, HashSet<NondeterministicState>> Transactions { get; } = [];
public bool Equals(NondeterministicState? other) => other is not null && Id.Equals(other.Id);
public void AddTransaction(EmptyChar c, NondeterministicState state)
{
if (Transactions.TryGetValue(c, out HashSet<NondeterministicState>? states))
{
states.Add(state);
}
else
{
Transactions.Add(c, [state]);
}
}
public override bool Equals(object? obj) => obj is NondeterministicState other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
public HashSet<NondeterministicState> CalculateEmptyClosure()
{
HashSet<NondeterministicState> result = [];
Queue<NondeterministicState> queue = [];
queue.Enqueue(this);
while (queue.TryDequeue(out NondeterministicState? state))
{
result.Add(state);
if (!state.Transactions.TryGetValue(EmptyChar.Empty, out HashSet<NondeterministicState>? next))
{
continue;
}
foreach (NondeterministicState s in next.Where(s => !result.Contains(s)))
{
queue.Enqueue(s);
}
}
return result;
}
}
public class NondeterministicStateSet(HashSet<NondeterministicState> states) : IEquatable<NondeterministicStateSet>
{
private readonly HashSet<NondeterministicState> _states = states;
public bool Equals(NondeterministicStateSet? other)
{
if (other is null)
{
return false;
}
return _states.Count == other._states.Count && _states.All(s => other._states.Contains(s));
}
public override bool Equals(object? obj) => obj is NondeterministicStateSet other && Equals(other);
public override int GetHashCode() => _states.Aggregate(0, (current, state) => current ^ state.GetHashCode());
}
public class NondeterministicFiniteAutomation(NondeterministicState start, HashSet<NondeterministicState> finalStates)
{
public NondeterministicState Start { get; } = start;
public HashSet<NondeterministicState> FinalStates { get; } = finalStates;
}

View File

@ -1,230 +0,0 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public abstract class RegularExpression
{
public abstract NondeterministicFiniteAutomation Convert2Nfa();
/// <summary>
/// 匹配空字符串
/// </summary>
public static RegularExpression Empty => new EmptyExpression();
/// <summary>
/// 匹配单个字符
/// c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static RegularExpression Single(char c) => new SymbolExpression(c);
/// <summary>
/// left|right
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static RegularExpression Alternate(RegularExpression left, RegularExpression right) =>
new AlternationExpression(left, right);
public static RegularExpression operator |(RegularExpression left, RegularExpression right) =>
new AlternationExpression(left, right);
/// <summary>
/// left-right
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static RegularExpression Concatenate(RegularExpression first, RegularExpression second) =>
new ConcatenationExpression(first, second);
public static RegularExpression operator +(RegularExpression left, RegularExpression right) =>
new ConcatenationExpression(left, right);
/// <summary>
/// inner*
/// </summary>
/// <param name="inner"></param>
/// <returns></returns>
public static RegularExpression Kleene(RegularExpression inner) => new KleeneExpression(inner);
/// <summary>
/// value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static RegularExpression String(string value) => new StringExpression(value);
public static RegularExpression CharSetOf(string value) => new CharSetExpression(value.ToCharArray());
public static RegularExpression CharSetOf(Func<char, bool> predicate)
=> new CharSetExpression(Iterate(char.MinValue, char.MaxValue).Where(predicate).ToArray());
/// <summary>
/// [a-b]
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static RegularExpression Range(char a, char b) => new CharSetExpression(Iterate(a, b).ToArray());
private static IEnumerable<char> Iterate(char a, char b)
{
for (char c = a; c <= b; c++)
{
if (c == char.MaxValue)
{
yield break;
}
yield return c;
}
}
}
public class EmptyExpression : RegularExpression
{
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicState final = new();
NondeterministicState start = new();
start.AddTransaction(EmptyChar.Empty, final);
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class SymbolExpression(char symbol) : RegularExpression
{
public char Symbol { get; } = symbol;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicState final = new();
NondeterministicState start = new();
start.AddTransaction(new EmptyChar(Symbol), final);
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class AlternationExpression(RegularExpression left, RegularExpression right) : RegularExpression
{
public RegularExpression Left { get; } = left;
public RegularExpression Right { get; } = right;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicFiniteAutomation left = Left.Convert2Nfa();
NondeterministicFiniteAutomation right = Right.Convert2Nfa();
NondeterministicState final = new();
foreach (NondeterministicState state in left.FinalStates.Concat(right.FinalStates))
{
state.AddTransaction(EmptyChar.Empty, final);
}
NondeterministicState start = new();
start.AddTransaction(EmptyChar.Empty, left.Start);
start.AddTransaction(EmptyChar.Empty, right.Start);
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class ConcatenationExpression(RegularExpression first, RegularExpression second) : RegularExpression
{
public RegularExpression First { get; } = first;
public RegularExpression Second { get; } = second;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicFiniteAutomation first = First.Convert2Nfa();
NondeterministicFiniteAutomation second = Second.Convert2Nfa();
foreach (NondeterministicState state in first.FinalStates)
{
state.AddTransaction(EmptyChar.Empty, second.Start);
}
return new NondeterministicFiniteAutomation(first.Start, second.FinalStates);
}
}
public class KleeneExpression(RegularExpression inner) : RegularExpression
{
public RegularExpression Inner { get; } = inner;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicFiniteAutomation inner = Inner.Convert2Nfa();
NondeterministicState final = new();
final.AddTransaction(EmptyChar.Empty, inner.Start);
foreach (NondeterministicState state in inner.FinalStates)
{
state.AddTransaction(EmptyChar.Empty, final);
}
return new NondeterministicFiniteAutomation(final, [final]);
}
}
public class CharSetExpression : RegularExpression
{
public char[] Set { get; }
public CharSetExpression(Span<char> set)
{
if (set.Length == 0)
{
throw new InvalidOperationException();
}
Set = set.ToArray();
}
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicState start = new();
NondeterministicState final = new();
foreach (char c in Set)
{
start.AddTransaction(new EmptyChar(c), final);
}
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class StringExpression : RegularExpression
{
public string Word { get; }
public StringExpression(string word)
{
if (string.IsNullOrEmpty(word))
{
throw new InvalidOperationException();
}
Word = word;
}
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicState start = new();
NondeterministicState final = Word.Aggregate(start, (state, c) =>
{
NondeterministicState next = new();
state.AddTransaction(new EmptyChar(c), next);
return next;
});
return new NondeterministicFiniteAutomation(start, [final]);
}
}

View File

@ -1,78 +0,0 @@
using CanonSharp.Common.Abstractions;
namespace CanonSharp.Common.Reader;
public class SourceReader : ISourceReader
{
private readonly StreamReader _reader;
private char? _lookAhead;
public SourceReader(string filename)
{
FileInfo source = new(filename);
if (!source.Exists)
{
throw new InvalidOperationException();
}
_reader = new StreamReader(filename);
}
public char Read()
{
if (_lookAhead.HasValue)
{
char result = _lookAhead.Value;
_lookAhead = null;
return result;
}
if (!TryFetchChar(out char c))
{
throw new InvalidOperationException();
}
return c;
}
public bool TryPeek(out char c)
{
if (_lookAhead.HasValue)
{
c = _lookAhead.Value;
return true;
}
if (!TryFetchChar(out c))
{
return false;
}
_lookAhead = c;
return true;
}
private readonly char[] _buffer = new char[1024];
private int _length;
private int _count;
private bool TryFetchChar(out char c)
{
if (_length == _count)
{
_length = _reader.Read(_buffer);
_count = 0;
}
if (_length == 0)
{
c = char.MinValue;
return false;
}
c = _buffer[_count];
_count += 1;
return true;
}
}

View File

@ -1,33 +0,0 @@
using CanonSharp.Common.Abstractions;
namespace CanonSharp.Common.Reader;
public class StringReader(string source) : ISourceReader
{
private int _pos;
public char Read()
{
if (_pos >= source.Length)
{
throw new InvalidOperationException();
}
char result = source[_pos];
_pos += 1;
return result;
}
public bool TryPeek(out char c)
{
if (_pos < source.Length)
{
c = source[_pos];
return true;
}
c = char.MinValue;
return false;
}
}

View File

@ -0,0 +1,150 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using static CanonSharp.Combinator.Text.TextParserBuilder;
using static CanonSharp.Combinator.ParserBuilder;
namespace CanonSharp.Common.Scanner;
public sealed class LexicalScanner
{
private readonly Parser<char, IEnumerable<LexicalToken>> _parser = PascalParser();
public IEnumerable<LexicalToken> Tokenize<TState>(TState state)
where TState : IReadState<char, TState>
{
return _parser.Parse(state).Value;
}
public static Parser<char, LexicalToken> KeywordParser()
{
return from value in Choice(StringIgnoreCase("program"),
StringIgnoreCase("const"),
StringIgnoreCase("var"),
StringIgnoreCase("procedure"),
StringIgnoreCase("function"),
StringIgnoreCase("begin"),
StringIgnoreCase("end"),
StringIgnoreCase("array"),
StringIgnoreCase("of"),
StringIgnoreCase("if"),
StringIgnoreCase("then"),
StringIgnoreCase("else"),
StringIgnoreCase("for"),
StringIgnoreCase("to"),
StringIgnoreCase("do"),
StringIgnoreCase("integer"),
StringIgnoreCase("real"),
StringIgnoreCase("boolean"),
StringIgnoreCase("char"),
StringIgnoreCase("divide"),
StringIgnoreCase("not"),
StringIgnoreCase("mod"),
StringIgnoreCase("and"),
StringIgnoreCase("or"),
StringIgnoreCase("true"),
StringIgnoreCase("false"),
StringIgnoreCase("while"))
from _ in (AsciiLetter() | AsciiDigit() | Char('_')).LookAhead().Not()
select new LexicalToken(LexicalTokenType.Keyword, value);
}
public static Parser<char, LexicalToken> DelimiterParser()
{
Parser<char, LexicalToken> semicolonParser = from token in Char(':')
from _ in Char('=').LookAhead().Not()
select new LexicalToken(LexicalTokenType.Delimiter, token.ToString());
Parser<char, LexicalToken> periodParser = from token in Char('.')
from _ in Char('.').LookAhead().Not()
select new LexicalToken(LexicalTokenType.Delimiter, ".");
Parser<char, LexicalToken> singleCharTokenParser = from token in Choice(
String(","),
String(";"),
String("("),
String(")"),
String("["),
String("]"),
String(".."))
select new LexicalToken(LexicalTokenType.Delimiter, token);
return singleCharTokenParser | semicolonParser | periodParser;
}
public static Parser<char, LexicalToken> OperatorParser()
{
Parser<char, LexicalToken> lessParser = from token in Char('<')
from _ in Char('=').LookAhead().Not()
select new LexicalToken(LexicalTokenType.Operator, "<");
Parser<char, LexicalToken> greaterParser = from token in Char('>')
from _ in Char('=').LookAhead().Not()
select new LexicalToken(LexicalTokenType.Operator, ">");
Parser<char, LexicalToken> otherParsers = from token in Choice(
String("="),
String("!="),
String("<="),
String(">="),
String("+"),
String("-"),
String("*"),
String("/"),
String(":="))
select new LexicalToken(LexicalTokenType.Operator, token);
return otherParsers | lessParser | greaterParser;
}
public static Parser<char, LexicalToken> ConstIntegerParser()
{
return from nums in AsciiDigit().Many1()
from _ in Char('.').LookAhead().Not()
select new LexicalToken(LexicalTokenType.ConstInteger, new string(nums.ToArray()));
}
public static Parser<char, LexicalToken> ConstFloatParser()
{
return from integer in AsciiDigit().Many1()
from _ in Char('.')
from fraction in AsciiDigit().Many1()
select new LexicalToken(LexicalTokenType.ConstFloat,
new string(integer.ToArray()) + '.' + new string(fraction.ToArray()));
}
public static Parser<char, LexicalToken> IdentifierParser()
{
return from first in AsciiLetter() | Char('_')
from second in (AsciiLetter() | AsciiDigit() | Char('_')).Many()
select new LexicalToken(LexicalTokenType.Identifier, first + new string(second.ToArray()));
}
public static Parser<char, Unit> CommentParser()
{
return Any<char>().Quote(Char('{'), Char('}')).Map(_ => Unit.Instance);
}
public static Parser<char, Unit> JunkParser()
{
return Space().Map(_ => Unit.Instance) | LineBreak().Map(_ => Unit.Instance) | CommentParser();
}
public static Parser<char, LexicalToken> CharParser()
{
return from str in Any<char>().Quote(Char('\'')).Map(x => new string(x.ToArray()))
select str.Length <= 1
? new LexicalToken(LexicalTokenType.Character, str)
: new LexicalToken(LexicalTokenType.String, str);
}
public static Parser<char, IEnumerable<LexicalToken>> PascalParser()
{
return JunkParser().SkipTill(Choice(KeywordParser(),
DelimiterParser(),
OperatorParser(),
ConstIntegerParser(),
ConstFloatParser(),
CharParser(),
IdentifierParser())).Many();
}
}

View File

@ -0,0 +1,29 @@
namespace CanonSharp.Common.Scanner;
public enum LexicalTokenType
{
Keyword,
ConstInteger,
ConstFloat,
Operator,
Delimiter,
Identifier,
Character,
String
}
public sealed class LexicalToken(LexicalTokenType type, string literalValue) : IEquatable<LexicalToken>
{
public LexicalTokenType TokenType { get; } = type;
public string LiteralValue { get; } = literalValue;
public bool Equals(LexicalToken? other) =>
other is not null && TokenType == other.TokenType && LiteralValue == other.LiteralValue;
public override bool Equals(object? obj) => obj is LexicalToken other && Equals(other);
public override int GetHashCode() => TokenType.GetHashCode() ^ LiteralValue.GetHashCode();
public override string ToString() => $"<{TokenType}>'{LiteralValue}'";
}

View File

@ -0,0 +1,62 @@
using CanonSharp.Combinator.Abstractions;
namespace CanonSharp.Common.Scanner;
/// <summary>
/// 字符串输入流状态
/// </summary>
public sealed class StringReadState : IReadState<char, StringReadState>
{
private readonly string _source;
private readonly int _index;
public char Current => _source[_index];
public bool HasValue => _index < _source.Length;
public StringReadState Next => new(_source, _index + 1);
private StringReadState(string source, int index)
{
_source = source;
_index = index;
}
public StringReadState(string source) : this(source, 0)
{
}
public bool Equals(StringReadState? other)
{
if (other is null)
{
return false;
}
return _source == other._source && _index == other._index;
}
public override bool Equals(object? obj) => obj is StringReadState other && Equals(other);
public override int GetHashCode() => _source.GetHashCode() ^ _index;
public override string ToString()
{
return HasValue ? $"{ToReadableString(Current)}<0x{(int)Current:X2}>" : "End of string.";
}
private static string ToReadableString(char token)
=> token switch
{
'\0' => "\\0",
'\a' => "\\a",
'\b' => "\\b",
'\f' => "\\f",
'\n' => "\\n",
'\r' => "\\r",
'\t' => "\\t",
'\v' => "\\v",
_ => token.ToString(),
};
}

View File

@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>

View File

@ -24,7 +24,6 @@
<ItemGroup>
<ProjectReference Include="..\CanonSharp.Common\CanonSharp.Common.csproj" />
<ProjectReference Include="..\CanonSharp.Parser\CanonSharp.Parser.fsproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,84 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Tests.Utils;
using static CanonSharp.Combinator.ParserBuilder;
namespace CanonSharp.Tests.CombinatorsTests;
public class BasicParsersTests : ParserTestsBase
{
[Fact]
public void AlternativeTest()
{
Parser<char, char> parser = Token('a') | Token('b');
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateSuccessfulResult(parser, 'b', "bcd");
ValidateFailedResult(parser, "cde");
parser = Token('a').Alternative(_ => Token('b'));
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateSuccessfulResult(parser, 'b', "bcd");
ValidateFailedResult(parser, "cde");
}
[Fact]
public void BindTest()
{
Parser<char, char> parser = Token('a').Bind(_ => Token('b')).Bind(_ => Token('c'));
ValidateSuccessfulResult(parser, 'c', "abc");
ValidateFailedResult(parser, "acd");
ValidateFailedResult(parser, "ab");
}
[Fact]
public void MapTest()
{
Parser<char, string> parser = Token('a').Map(c => $"{c}");
ValidateSuccessfulResult(parser, "a", "abc");
parser = Token('a').Map("test");
ValidateSuccessfulResult(parser, "test", "abc");
}
[Fact]
public void NextTest()
{
Parser<char, char> parser = Token('a').Next(_ => Token('a'), _ => Token('b'));
ValidateSuccessfulResult(parser, 'a', "aaa");
ValidateSuccessfulResult(parser, 'b', "bbb");
parser = Token('a').Next(_ => Token('a'), _ => 'b');
ValidateSuccessfulResult(parser, 'b', "bbb");
parser = Token('a').Next(_ => Pure<char, char>('1'), '2');
ValidateSuccessfulResult(parser, '1', "aaa");
ValidateSuccessfulResult(parser, '2', "bbb");
}
[Fact]
public void NextTest2()
{
Parser<char, string> parser = Token('a').Next(_ => "123", _ => Pure<char, string>("456"));
ValidateSuccessfulResult(parser, "123", "aaa");
ValidateSuccessfulResult(parser, "456", "bbb");
parser = Token('a').Next(_ => "123", _ => "456");
ValidateSuccessfulResult(parser, "123", "aaa");
ValidateSuccessfulResult(parser, "456", "bbb");
parser = Token('a').Next(_ => "123", "456");
ValidateSuccessfulResult(parser, "123", "aaa");
ValidateSuccessfulResult(parser, "456", "bbb");
}
[Fact]
public void FixTest()
{
Parser<char, char> parser = Fix<char, Unit>(self => Token('a').Next(_ => self, Unit.Instance))
.Bind(_ => Token('b'));
ValidateSuccessfulResult(parser, 'b', "aaaaab");
}
}

View File

@ -0,0 +1,176 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Tests.Utils;
using static CanonSharp.Combinator.ParserBuilder;
namespace CanonSharp.Tests.CombinatorsTests;
public class CombinatorParserTests : ParserTestsBase
{
[Fact]
public void ChoiceTest()
{
Parser<char, char> parser = Choice(Token('a'), Token('b'), Token('c'));
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateSuccessfulResult(parser, 'b', "bcd");
ValidateSuccessfulResult(parser, 'c', "cde");
parser = Choice([Token('a'), Token('b'), Token('c')]);
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateSuccessfulResult(parser, 'b', "bcd");
ValidateSuccessfulResult(parser, 'c', "cde");
}
[Fact]
public void SequenceTest()
{
Parser<char, IEnumerable<char>> parser = Sequence(Token('a'), Token('b'), Token('c'));
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "abc");
parser = Sequence([Token('a'), Token('b'), Token('c')]);
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "abc");
}
[Fact]
public void LeftRightTest()
{
Parser<char, char> parser = Token('a').Left(Token('b'));
ValidateSuccessfulResult(parser, 'a', "ab");
parser = Token('a').Right(Token('b'));
ValidateSuccessfulResult(parser, 'b', "ab");
}
[Fact]
public void ManyTest()
{
Parser<char, IEnumerable<char>> parser = Token('a').Many();
ValidateSuccessfulResult(parser, [], "bbb");
ValidateSuccessfulResult(parser, ['a', 'a', 'a'], "aaa");
parser = Token('a').Many1();
ValidateSuccessfulResult(parser, ['a', 'a'], "aa");
ValidateFailedResult(parser, "bbb");
}
[Fact]
public void SkipManyTest()
{
Parser<char, char> parser = Token('a').SkipMany().Right(Token('b'));
ValidateSuccessfulResult(parser, 'b', "aaaab");
ValidateSuccessfulResult(parser, 'b', "bbbb");
parser = Token('a').SkipMany1().Right(Token('b'));
ValidateSuccessfulResult(parser, 'b', "aaaaaab");
ValidateFailedResult(parser, "bb");
}
[Fact]
public void ChainTest()
{
// 等效于Many1
// 但是不返回中间结果
Parser<char, char> parser = Token('a').Chain(Token);
ValidateSuccessfulResult(parser, 'a', "aa");
ValidateFailedResult(parser, "bb");
parser = Token('_').Chain(x => x == '_' ? Satisfy<char>(char.IsLetter) : Satisfy<char>(char.IsDigit));
ValidateSuccessfulResult(parser, '1', "_a1");
ValidateSuccessfulResult(parser, '_', "_123");
}
[Fact]
public void ManyTillTest()
{
Parser<char, IEnumerable<char>> parser = Token('a').ManyTill(Token('b').LookAhead());
ValidateSuccessfulResult(parser, ['a', 'a', 'a'], "aaab");
ValidateSuccessfulResult(parser, [], "b");
parser = Token('a').Many1Till(Token('b').LookAhead());
ValidateSuccessfulResult(parser, ['a', 'a'], "aab");
ValidateFailedResult(parser, "bb");
}
[Fact]
public void SkipTillTest()
{
Parser<char, char> parser = Token('a').SkipTill(Token('b'));
ValidateSuccessfulResult(parser, 'b', "aaab");
ValidateSuccessfulResult(parser, 'b', "b");
parser = Token('a').Skip1Till(Token('b'));
ValidateSuccessfulResult(parser, 'b', "aaab");
ValidateFailedResult(parser, "b");
}
[Fact]
public void TakeTillTest()
{
Parser<char, IEnumerable<char>> parser = TakeTill(Token('b').LookAhead());
ValidateSuccessfulResult(parser, ['a', 'a'], "aab");
ValidateSuccessfulResult(parser, [], "b");
parser = Take1Till(Token('b').LookAhead());
ValidateSuccessfulResult(parser, ['a', 'a'], "aab");
ValidateFailedResult(parser, "b");
}
[Fact]
public void MatchTest()
{
Parser<char, char> parser = Token('b').Match();
ValidateSuccessfulResult(parser, 'b', "asdfasdfasdfasdfb");
ValidateSuccessfulResult(parser, 'b', "b");
}
[Fact]
public void QuoteTest()
{
Parser<char, IEnumerable<char>> parser = Any<char>().Quote(Token('['), Token(']'));
ValidateSuccessfulResult(parser, ['1', '2', '3'], "[123]");
parser = Any<char>().Quote(Token('\''));
ValidateSuccessfulResult(parser, ['1', '2', '3'], "'123'");
}
[Fact]
public void SeparatedByTest()
{
Parser<char, IEnumerable<char>> parser = Token('a').SeparatedBy(Token(','));
ValidateSuccessfulResult(parser, ['a', 'a', 'a'], "a,a,a");
ValidateSuccessfulResult(parser, ['a'], "a");
ValidateSuccessfulResult(parser, [], "");
parser = Token('a').SeparatedBy1(Token(','));
ValidateSuccessfulResult(parser, ['a', 'a', 'a'], "a,a,a");
ValidateSuccessfulResult(parser, ['a'], "a");
ValidateFailedResult(parser, "");
}
[Fact]
public void EndByTest()
{
Parser<char, IEnumerable<char>> parser = Satisfy<char>(char.IsLetter).EndBy(Token('.'));
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "abc.");
ValidateSuccessfulResult(parser, [], ".");
parser = Satisfy<char>(char.IsLetter).EndBy1(Token('.'));
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "abc.");
ValidateFailedResult(parser, ".");
}
[Fact]
public void SeparatedOrEndByTest()
{
Parser<char, IEnumerable<char>> parser = Satisfy<char>(char.IsLetter).SeparatedOrEndBy1(Token(','));
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "a,b,c,");
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "a,b,c");
ValidateFailedResult(parser, "");
parser = Satisfy<char>(char.IsLetter).SeparatedOrEndBy(Token(','));
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "a,b,c,");
ValidateSuccessfulResult(parser, ['a', 'b', 'c'], "a,b,c");
ValidateSuccessfulResult(parser, [], "");
}
}

View File

@ -0,0 +1,29 @@
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using static CanonSharp.Combinator.Text.TextParserBuilder;
using CanonSharp.Tests.Utils;
namespace CanonSharp.Tests.CombinatorsTests;
public class LinqTests : ParserTestsBase
{
[Fact]
public void SelectTest1()
{
Parser<char, string> parser = from token in Char('a')
select token.ToString();
ValidateSuccessfulResult(parser, "a", "a");
ValidateFailedResult(parser, "b");
}
[Fact]
public void SelectManyTest1()
{
Parser<char, int> parser = from _1 in Char('a')
from _2 in Char('b')
from _3 in Char('c')
select 123;
ValidateSuccessfulResult(parser, 123, "abc");
ValidateFailedResult(parser, "asd");
}
}

View File

@ -0,0 +1,51 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Tests.Utils;
using static CanonSharp.Combinator.ParserBuilder;
using static CanonSharp.Combinator.Text.TextParserBuilder;
namespace CanonSharp.Tests.CombinatorsTests;
public class ModifierParserTests : ParserTestsBase
{
[Fact]
public void DoTest()
{
Parser<char, char> parser = Token('a').Do(x => Assert.Equal('a', x)).Do(x => Assert.Equal('a', x),
failedResult => Assert.ThrowsAny<ParseException>(() => failedResult.Value));
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateFailedResult(parser, "bcd");
}
[Fact]
public void LookAheadTest()
{
Parser<char, char> parser = Token('a').LookAhead().Next(_ => Token('a'), _ => Token('b'));
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateSuccessfulResult(parser, 'b', "bcd");
}
[Fact]
public void NotTest()
{
Parser<char, char> parser = Token('a').Not('b');
ValidateSuccessfulResult(parser, 'b', "bcd");
parser = Token('a').Not().Bind(_ => Token('b'));
ValidateSuccessfulResult(parser, 'b', "bcd");
}
[Fact]
public void TryTest()
{
Parser<char, string> parser = String("abc").Try("cde");
ValidateSuccessfulResult(parser, "abc", "abc");
ValidateSuccessfulResult(parser, "cde", "cde");
parser = String("abc").Try(_ => "cde");
ValidateSuccessfulResult(parser, "abc", "abc");
ValidateSuccessfulResult(parser, "cde", "cde");
}
}

View File

@ -0,0 +1,84 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Tests.Utils;
using static CanonSharp.Combinator.ParserBuilder;
namespace CanonSharp.Tests.CombinatorsTests;
public class PrimitiveParserTests : ParserTestsBase
{
[Fact]
public void PureTest()
{
Parser<char, char> parser = Pure<char, char>('a');
ValidateSuccessfulResult(parser, 'a', "abc");
parser = Pure<char, char>(_ => 'a');
ValidateSuccessfulResult(parser, 'a', "abc");
}
[Fact]
public void NullTest()
{
Parser<char, Unit> parser = Null<char>();
ValidateSuccessfulResult(parser, Unit.Instance, "abc");
}
[Fact]
public void FailTest()
{
Parser<char, char> parser = Fail<char, char>();
ValidateFailedResult(parser, "abc");
parser = Fail<char, char>("Failed message");
FailedResult<char, char> result = ValidateFailedResult(parser, "abc");
Assert.Equal("Failed message", result.Message);
parser = Fail<char, char>(x => $"{x}");
result = ValidateFailedResult(parser, "abc");
Assert.Equal("a<0x61>", result.Message);
parser = Fail<char, char>(new InvalidOperationException());
result = ValidateFailedResult(parser, "abc");
Assert.IsType<InvalidOperationException>(result.Exception.InnerException);
}
[Fact]
public void SatisfyTest()
{
Parser<char, char> parser = Satisfy<char>(char.IsLetter);
ValidateSuccessfulResult(parser, 'a', "abc");
ValidateFailedResult(parser, "123");
}
[Fact]
public void AnyTest()
{
Parser<char, char> parser = Any<char>();
ValidateSuccessfulResult(parser, '1', "123");
}
[Fact]
public void TokenTest()
{
Parser<char, char> parser = Token('a');
ValidateSuccessfulResult(parser, 'a', "abc");
}
[Fact]
public void TakeTest()
{
Parser<char, IEnumerable<char>> parser = Take<char>(5);
ValidateSuccessfulResult(parser, ['h', 'e', 'l', 'l', 'o'], "hello");
ValidateFailedResult(parser, "abc");
}
[Fact]
public void SkipTest()
{
Parser<char, char> parser = Skip<char>(5).Bind(_ => Token(','));
ValidateSuccessfulResult(parser, ',', "hello,world.");
ValidateFailedResult(parser, "abc");
}
}

View File

@ -1,25 +0,0 @@
using CanonSharp.Common.Abstractions;
using StringReader = CanonSharp.Common.Reader.StringReader;
namespace CanonSharp.Tests.LexicalAnalyzerTests;
public class ReaderTests
{
[Fact]
public void StringReaderTest()
{
StringReader reader = new("ab");
Assert.True(reader.TryPeek(out char c));
Assert.Equal('a', c);
Assert.True(reader.TryPeek(out c));
Assert.Equal('a', c);
Assert.Equal('a', reader.Read());
Assert.True(reader.TryPeek(out c));
Assert.Equal('b', c);
Assert.True(reader.TryPeek(out c));
Assert.Equal('b', c);
Assert.Equal('b', reader.Read());
Assert.False(reader.TryPeek(out c));
}
}

View File

@ -1,104 +0,0 @@
using CanonSharp.Common.LexicalAnalyzer;
namespace CanonSharp.Tests.LexicalAnalyzerTests;
public class RegularExpressionTests
{
[Fact]
public void KleeneTest()
{
RegularExpression expression = RegularExpression.Concatenate(
RegularExpression.Kleene(RegularExpression.Single('a')),
RegularExpression.Single('b'));
NondeterministicFiniteAutomation automation = expression.Convert2Nfa();
automation.Start.Transactions.TryGetValue(EmptyChar.Empty, out HashSet<NondeterministicState>? next);
Assert.NotNull(next);
Assert.Contains(next, s => s.Transactions.ContainsKey(new EmptyChar('a')));
Assert.Contains(next, s => s.Transactions.ContainsKey(new EmptyChar('b')));
}
[Fact]
public void AlternateTest()
{
RegularExpression expression = RegularExpression.Alternate(
RegularExpression.Kleene(RegularExpression.Single('a')),
RegularExpression.Single('b'));
NondeterministicFiniteAutomation automation = expression.Convert2Nfa();
automation.Start.Transactions.TryGetValue(EmptyChar.Empty, out HashSet<NondeterministicState>? next);
Assert.NotNull(next);
Assert.Contains(next, s => s.Transactions.ContainsKey(new EmptyChar('b')));
NondeterministicState? state = (from item in next
where item.Transactions[EmptyChar.Empty].Count == 2
select item).FirstOrDefault();
Assert.NotNull(state);
Assert.Contains(state.Transactions[EmptyChar.Empty],
s => s.Transactions.ContainsKey(new EmptyChar('a')));
}
[Fact]
public void RangeTest()
{
RegularExpression expression = RegularExpression.Range('a', 'z');
NondeterministicFiniteAutomation nfa = expression.Convert2Nfa();
Assert.Equal(26, nfa.Start.Transactions.Count);
}
[Fact]
public void ConvertTest()
{
RegularExpression expression = RegularExpression.Alternate(
RegularExpression.Kleene(RegularExpression.Single('a')),
RegularExpression.Single('b'));
NondeterministicFiniteAutomation automation = expression.Convert2Nfa();
DeterministicFiniteAutomation dfa = DeterministicFiniteAutomation.Create(automation);
DeterministicState state2 = dfa.Start.Transaction['a'];
Assert.Equal(state2, state2.Transaction['a']);
DeterministicState state3 = dfa.Start.Transaction['b'];
Assert.Empty(state3.Transaction);
Assert.Equal(3, dfa.FinalStates.Count);
}
[Fact]
public void NondeterministicStateSetTest()
{
Dictionary<NondeterministicStateSet, char> map = [];
NondeterministicState key1 = new();
NondeterministicState key2 = new();
map.Add(new NondeterministicStateSet([key1, key2]), 'a');
Assert.Equal('a', map[new NondeterministicStateSet([key2, key1])]);
}
[Fact]
public void PrefixConvertTest()
{
RegularExpression expression = RegularExpression.Alternate(
RegularExpression.String("string"),
RegularExpression.String("string1"));
NondeterministicFiniteAutomation nfa = expression.Convert2Nfa();
DeterministicFiniteAutomation.Create(nfa);
}
[Fact]
public void WhiteSpaceConvertTest()
{
NondeterministicFiniteAutomation nfa = LexicalToken.WhiteSpace.Expression.Convert2Nfa();
DeterministicFiniteAutomation.Create(nfa);
}
}

View File

@ -1,71 +0,0 @@
using CanonSharp.Common.LexicalAnalyzer;
using StringReader = CanonSharp.Common.Reader.StringReader;
namespace CanonSharp.Tests.LexicalAnalyzerTests;
public class ScanTests
{
[Fact]
public void ScanTest1()
{
LexicalScannerBuilder builder = LexicalScanner.CreateEmptyBuilder();
LexicalToken token1 = new(RegularExpression.String("ab"), 1);
builder.DefineToken(token1);
StringReader reader = new("ab");
LexicalScanner scanner = builder.Build(reader);
Assert.True(scanner.TryRead(out LexicalToken? result));
Assert.Equal(token1, result);
Assert.Equal("ab", result.LiteralValue);
}
[Fact]
public void ScanTest2()
{
LexicalScannerBuilder builder = LexicalScanner.CreateDefaultBuilder();
LexicalToken stringKeyword = new(RegularExpression.String("string"), 100);
LexicalToken periodDelimiter = new(RegularExpression.Single('.'), 100);
LexicalToken semiColonDelimiter = new(RegularExpression.Single(';'), 100);
LexicalToken identifier = new(RegularExpression.Concatenate(RegularExpression.Range('a', 'z'),
RegularExpression.Kleene(RegularExpression.Range('a', 'z'))), 0);
LexicalToken assigner = new(RegularExpression.String(":="), 100);
builder.DefineToken(stringKeyword);
builder.DefineToken(periodDelimiter);
builder.DefineToken(semiColonDelimiter);
builder.DefineToken(identifier);
builder.DefineToken(assigner);
StringReader reader = new("""
string value := origin;
string abc := value.
""");
LexicalScanner scanner = builder.Build(reader);
Validate(scanner, [
stringKeyword,
identifier,
assigner,
identifier,
semiColonDelimiter,
stringKeyword,
identifier,
assigner,
identifier,
periodDelimiter
]);
Assert.False(scanner.TryRead(out _));
}
private static void Validate(LexicalScanner scanner, IEnumerable<LexicalToken> expectedTokens)
{
foreach (LexicalToken token in expectedTokens)
{
Assert.True(scanner.TryRead(out LexicalToken? outToken));
Assert.NotNull(outToken);
Assert.Equal(token, outToken);
}
}
}

View File

@ -0,0 +1,21 @@
using CanonSharp.Combinator.Extensions;
using CanonSharp.Common.Scanner;
namespace CanonSharp.Tests.LexicalAnalyzerTests;
public class StringReadStateTests
{
[Fact]
public void AsEnumerableTest()
{
StringReadState state = new("abc");
IEnumerable<StringReadState> states = state.AsEnumerable<char, StringReadState>();
foreach ((char c, StringReadState s) in "abc".Zip(states))
{
Assert.True(s.HasValue);
Assert.Equal(c, s.Current);
}
}
}

View File

@ -0,0 +1,167 @@
using CanonSharp.Common.Scanner;
using CanonSharp.Tests.Utils;
namespace CanonSharp.Tests.ScannerTest;
public class LexicalParserTests : LexicalTestBase
{
[Fact]
public void LexicalParserTest1()
{
const string pascalProgram = """
program HelloWorld;
var
message: char;
begin
message := 'h';
writeln(message);
end.
""";
ValidateLexicalTokens(LexicalScanner.PascalParser(), pascalProgram, [
(LexicalTokenType.Keyword, "program"),
(LexicalTokenType.Identifier, "HelloWorld"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "var"),
(LexicalTokenType.Identifier, "message"),
(LexicalTokenType.Delimiter, ":"),
(LexicalTokenType.Keyword, "char"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "begin"),
(LexicalTokenType.Identifier, "message"),
(LexicalTokenType.Operator, ":="),
(LexicalTokenType.Character, "h"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Identifier, "writeln"),
(LexicalTokenType.Delimiter, "("),
(LexicalTokenType.Identifier, "message"),
(LexicalTokenType.Delimiter, ")"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "end"),
(LexicalTokenType.Delimiter, ".")
]);
}
[Fact]
public void LexicalParserTest2()
{
const string program = """
program main;
var
ab: integer;
begin
ab := 3;
write(ab);
end.
""";
ValidateLexicalTokens(LexicalScanner.PascalParser(), program, [
(LexicalTokenType.Keyword, "program"),
(LexicalTokenType.Identifier, "main"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "var"),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Delimiter, ":"),
(LexicalTokenType.Keyword, "integer"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "begin"),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Operator, ":="),
(LexicalTokenType.ConstInteger, "3"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Identifier, "write"),
(LexicalTokenType.Delimiter, "("),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Delimiter, ")"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "end"),
(LexicalTokenType.Delimiter, ".")
]);
}
[Fact]
public void LexicalParserTest3()
{
const string pascalProgram = """
{test}
program main;
var
ab, ba: integer;
begin
ab := 3;
ba := 5;
ab := 5;
write(ab + ba);
end.
""";
ValidateLexicalTokens(LexicalScanner.PascalParser(), pascalProgram, [
(LexicalTokenType.Keyword, "program"),
(LexicalTokenType.Identifier, "main"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "var"),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Delimiter, ","),
(LexicalTokenType.Identifier, "ba"),
(LexicalTokenType.Delimiter, ":"),
(LexicalTokenType.Keyword, "integer"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "begin"),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Operator, ":="),
(LexicalTokenType.ConstInteger, "3"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Identifier, "ba"),
(LexicalTokenType.Operator, ":="),
(LexicalTokenType.ConstInteger, "5"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Operator, ":="),
(LexicalTokenType.ConstInteger, "5"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Identifier, "write"),
(LexicalTokenType.Delimiter, "("),
(LexicalTokenType.Identifier, "ab"),
(LexicalTokenType.Operator, "+"),
(LexicalTokenType.Identifier, "ba"),
(LexicalTokenType.Delimiter, ")"),
(LexicalTokenType.Delimiter, ";"),
(LexicalTokenType.Keyword, "end"),
(LexicalTokenType.Delimiter, ".")
]);
}
[Theory]
[InlineData("""
program exFunction;
var
a, b, ret : integer;
begin
a := 100;
b := 200;
{ calling a function to get max valued }
ret := a - b;
end.
""", 29)]
[InlineData("""
{
This is a block comment that does closed.
}
program CommentClosed;
""", 3)]
[InlineData("""
{}
program CommentClosed;
""", 3)]
public void LexicalParserTest(string input, int count)
{
LexicalScanner scanner = new();
List<LexicalToken> tokens = scanner.Tokenize(new StringReadState(input)).ToList();
Assert.Equal(count, tokens.Count);
}
}

View File

@ -0,0 +1,177 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Common.Scanner;
using CanonSharp.Tests.Utils;
namespace CanonSharp.Tests.ScannerTest;
public class LexicalTokenParserTest : LexicalTestBase
{
[Theory]
[InlineData("program")]
[InlineData("const")]
[InlineData("var")]
[InlineData("procedure")]
[InlineData("function")]
[InlineData("begin")]
[InlineData("end")]
[InlineData("array")]
[InlineData("of")]
[InlineData("if")]
[InlineData("then")]
[InlineData("else")]
[InlineData("for")]
[InlineData("to")]
[InlineData("do")]
[InlineData("true")]
[InlineData("false")]
[InlineData("while")]
public void KeywordParserTest(string literalValue)
{
Parser<char, LexicalToken> keyword = LexicalScanner.KeywordParser();
ValidateSuccessfulParser(keyword, LexicalTokenType.Keyword, literalValue, literalValue);
}
[Theory]
[InlineData("andOne")]
[InlineData("program1")]
[InlineData("todo")]
public void FailedKeywordParserTest(string input)
{
Parser<char, LexicalToken> keyword = LexicalScanner.KeywordParser();
ValidateFailedParser(keyword, input);
}
[Theory]
[InlineData(",")]
[InlineData(".")]
[InlineData(";")]
[InlineData(":")]
[InlineData("(")]
[InlineData(")")]
[InlineData("[")]
[InlineData("]")]
[InlineData("..")]
public void DelimiterParserTest(string literalValue)
{
Parser<char, LexicalToken> delimiter = LexicalScanner.DelimiterParser();
ValidateSuccessfulParser(delimiter, LexicalTokenType.Delimiter, literalValue, literalValue);
}
[Theory]
[InlineData(":=")]
public void FailedDelimiterParserTest(string input)
{
Parser<char, LexicalToken> delimiter = LexicalScanner.DelimiterParser();
ValidateFailedParser(delimiter, input);
}
[Theory]
[InlineData("=")]
[InlineData("!=")]
[InlineData(">")]
[InlineData(">=")]
[InlineData("<")]
[InlineData("<=")]
[InlineData("+")]
[InlineData("-")]
[InlineData("*")]
[InlineData("/")]
[InlineData(":=")]
public void OperatorParserTest(string literalValue)
{
Parser<char, LexicalToken> operatorParser = LexicalScanner.OperatorParser();
ValidateSuccessfulParser(operatorParser, LexicalTokenType.Operator, literalValue, literalValue);
}
[Theory]
[InlineData("identifier")]
[InlineData("_identifier")]
[InlineData("identifier123")]
[InlineData("identifier_with_underscore")]
[InlineData("CamelCase")]
[InlineData("andand")]
public void IdentifierParserTest(string literalValue)
{
Parser<char, LexicalToken> identifier = LexicalScanner.IdentifierParser();
ValidateSuccessfulParser(identifier, LexicalTokenType.Identifier, literalValue, literalValue);
}
[Theory]
[InlineData(123, "123")]
[InlineData(0, "0")]
public void ConstIntegerTest(int value, string input)
{
StringReadState state = new(input);
ParseResult<char, LexicalToken> result = LexicalScanner.ConstIntegerParser().Parse(state);
Assert.Equal(LexicalTokenType.ConstInteger, result.Value.TokenType);
Assert.Equal(value, int.Parse(result.Value.LiteralValue));
}
[Theory]
[InlineData(123.456, "123.456")]
[InlineData(0, "0.0")]
public void ConstFloatTest(double value, string input)
{
StringReadState state = new(input);
ParseResult<char, LexicalToken> result = LexicalScanner.ConstFloatParser().Parse(state);
Assert.Equal(LexicalTokenType.ConstFloat, result.Value.TokenType);
Assert.Equal(value, double.Parse(result.Value.LiteralValue));
}
[Theory]
[InlineData('a', "'a'")]
[InlineData('Z', "'Z'")]
public void CharTest(char value, string input)
{
StringReadState state = new(input);
ParseResult<char, LexicalToken> result = LexicalScanner.CharParser().Parse(state);
Assert.Equal(LexicalTokenType.Character, result.Value.TokenType);
Assert.Equal(value, char.Parse(result.Value.LiteralValue));
}
[Theory]
[InlineData("hello, world!", "'hello, world!'")]
public void StringTest(string value, string input)
{
StringReadState state = new(input);
ParseResult<char, LexicalToken> result = LexicalScanner.CharParser().Parse(state);
Assert.Equal(LexicalTokenType.String, result.Value.TokenType);
Assert.Equal(value, result.Value.LiteralValue);
}
[Theory]
[InlineData("{comment}")]
[InlineData("{}")]
public void CommentTest(string input)
{
StringReadState state = new(input);
ParseResult<char, Unit> result = LexicalScanner.CommentParser().Parse(state);
Assert.Equal(Unit.Instance, result.Value);
}
[Theory]
[InlineData(" {comment} program")]
[InlineData("""
{comment}
{comment}
{}{}{}{}
program
""")]
public void JunkTest(string input)
{
StringReadState state = new(input);
Parser<char, LexicalToken> parser = LexicalScanner.JunkParser().SkipTill(LexicalScanner.KeywordParser());
ParseResult<char, LexicalToken> result = parser.Parse(state);
Assert.Equal(LexicalTokenType.Keyword, result.Value.TokenType);
Assert.Equal("program", result.Value.LiteralValue);
}
}

View File

@ -0,0 +1,169 @@
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Combinator.Extensions;
using CanonSharp.Combinator.Text;
using CanonSharp.Common.Scanner;
using CanonSharp.Tests.Utils;
using static CanonSharp.Combinator.Text.TextParserBuilder;
namespace CanonSharp.Tests.TextTests;
public class TextParserTests : ParserTestsBase
{
[Fact]
public void CharTest()
{
ValidateSuccessfulResult(Char('a'), 'a', "abc");
ValidateSuccessfulResult(CharIgnoreCase('a'), 'a', "abc");
ValidateSuccessfulResult(CharIgnoreCase('a'), 'A', "ABC");
}
[Theory]
[InlineData('a', "a")]
[InlineData('b', "b")]
[InlineData('c', "c")]
[InlineData('d', "d")]
public void OneOfTest(char except, string input)
{
Parser<char, char> parser = OneOf("abcd");
ValidateSuccessfulResult(parser, except, input);
}
[Theory]
[InlineData('a', "a")]
[InlineData('b', "b")]
[InlineData('c', "c")]
[InlineData('d', "d")]
[InlineData('A', "A")]
[InlineData('B', "B")]
[InlineData('C', "C")]
[InlineData('D', "D")]
public void OneOfIgnoreCaseTest(char except, string input)
{
Parser<char, char> parser = OneOfIgnoreCase("abcd");
ValidateSuccessfulResult(parser, except, input);
}
[Theory]
[InlineData("hello,world.")]
[InlineData("HELLO,WORLD.")]
[InlineData("Hello,world.")]
[InlineData("Hello,World.")]
public void StringIgnoreCaseTest(string literalValue)
{
Parser<char, string> parser = StringIgnoreCase("hello,world.");
ValidateSuccessfulResult(parser, literalValue, literalValue);
}
[Theory]
[InlineData('0')]
[InlineData('5')]
[InlineData('9')]
public void RangeTest(char except)
{
Parser<char, char> parser = Range('0', '9');
ValidateSuccessfulResult(parser, except, except.ToString());
ValidateFailedResult(parser, "abc");
}
[Theory]
[InlineData('a')]
[InlineData('A')]
[InlineData('z')]
[InlineData('测')]
public void LetterTest(char except)
{
ValidateSuccessfulResult(Letter(), except, except.ToString());
}
[Theory]
[InlineData('0')]
[InlineData(',')]
[InlineData('%')]
public void FailedLetterTest(char except)
{
ValidateFailedResult(Letter(), except.ToString());
}
[Theory]
[InlineData('0')]
public void DigitTest(char except)
{
ValidateSuccessfulResult(Digit(), except, except.ToString());
}
[Theory]
[InlineData('a')]
[InlineData('A')]
[InlineData('z')]
[InlineData('测')]
[InlineData(',')]
[InlineData('%')]
public void FailedDigitTest(char except)
{
ValidateFailedResult(Digit(), except.ToString());
}
[Theory]
[InlineData('a')]
[InlineData('z')]
[InlineData('A')]
[InlineData('Z')]
public void AsciiLetterTest(char except)
{
ValidateSuccessfulResult(AsciiLetter(), except, except.ToString());
}
[Theory]
[InlineData('0')]
[InlineData(',')]
[InlineData('%')]
public void FailedAsciiLetterTest(char except)
{
ValidateFailedResult(AsciiLetter(), except.ToString());
}
[Theory]
[InlineData('0')]
public void AsciiDigitTest(char except)
{
ValidateSuccessfulResult(AsciiDigit(), except, except.ToString());
}
[Theory]
[InlineData('a')]
[InlineData('A')]
[InlineData('z')]
[InlineData('测')]
[InlineData(',')]
[InlineData('%')]
public void FailedAsciiDigitTest(char except)
{
ValidateFailedResult(AsciiDigit(), except.ToString());
}
[Fact]
public void SkipSpacesTest()
{
ValidateSuccessfulResult(String("test").SkipSpaces(), "test", " test");
ValidateSuccessfulResult(String("test").SkipSpaces(), "test", "\t test");
ValidateSuccessfulResult(String("test").SkipSpaces().Many(), ["test", "test", "test"],
"test test test test");
}
[Fact]
public void SkipSpaceAndLineBreakTest()
{
StringReadState state = new("""
test test test
test test
test
""");
Parser<char, IEnumerable<string>> parser = StringIgnoreCase("test").SkipSpaceAndLineBreak().Many();
ParseResult<char, IEnumerable<string>> result = parser.Parse(state);
Assert.All(result.Value, x => Assert.Equal("test", x.ToLower()));
}
}

View File

@ -0,0 +1,39 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Common.Scanner;
namespace CanonSharp.Tests.Utils;
public abstract class LexicalTestBase
{
protected static void ValidateSuccessfulParser(Parser<char, LexicalToken> parser, LexicalTokenType exceptedType,
string literalValue, string input)
{
StringReadState state = new(input);
ParseResult<char, LexicalToken> result = parser.Parse(state);
Assert.Equal(exceptedType, result.Value.TokenType);
Assert.Equal(literalValue, result.Value.LiteralValue);
}
protected static void ValidateFailedParser(Parser<char, LexicalToken> parser, string input)
{
StringReadState state = new(input);
ParseResult<char, LexicalToken> result = parser.Parse(state);
Assert.ThrowsAny<ParseException>(() => result.Value);
}
protected static void ValidateLexicalTokens(Parser<char, IEnumerable<LexicalToken>> parser, string input,
IEnumerable<(LexicalTokenType, string)> exceptedResult)
{
StringReadState state = new(input);
ParseResult<char, IEnumerable<LexicalToken>> result = parser.Parse(state);
foreach (((LexicalTokenType exceptedType, string exceptedValue), LexicalToken token) in exceptedResult.Zip(
result.Value))
{
Assert.Equal(exceptedType, token.TokenType);
Assert.Equal(exceptedValue, token.LiteralValue);
}
}
}

View File

@ -0,0 +1,43 @@
using CanonSharp.Combinator;
using CanonSharp.Combinator.Abstractions;
using CanonSharp.Common.Scanner;
namespace CanonSharp.Tests.Utils;
public abstract class ParserTestsBase
{
protected static void ValidateSuccessfulResult<T>(Parser<char, T> parser, T value, string source)
{
StringReadState state = new(source);
ParseResult<char, T> result = parser.Parse(state);
Assert.Equal(value, result.Value);
}
protected static void ValidateSuccessfulResult<T>(
Parser<char, IEnumerable<T>> parser,
IEnumerable<T> values, string source)
{
StringReadState state = new(source);
ParseResult<char, IEnumerable<T>> result = parser.Parse(state);
foreach ((T actual, T except) in result.Value.Zip(values))
{
Assert.Equal(except, actual);
}
}
protected static FailedResult<char, T> ValidateFailedResult<T>(Parser<char, T> parser, string source)
{
StringReadState state = new(source);
ParseResult<char, T> result = parser.Parse(state);
Assert.ThrowsAny<ParseException>(() =>
{
_ = result.Value;
});
return (FailedResult<char, T>)result;
}
}

View File

@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CanonSharp.Parser", "CanonSharp.Parser\CanonSharp.Parser.fsproj", "{A985BD0A-4AEF-44D6-BD36-5F8035A25ED7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CanonSharp.Tests", "CanonSharp.Tests\CanonSharp.Tests.csproj", "{5A28EF92-805F-44E9-8E13-EA9A5BB623BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CanonSharp.Common", "CanonSharp.Common\CanonSharp.Common.csproj", "{288943FE-E3E6-49E2-8C6F-9B5B9242266C}"
@ -16,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.gitea\workflows\unit-test.yaml = .gitea\workflows\unit-test.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CanonSharp.Combinator", "CanonSharp.Combinator\CanonSharp.Combinator.csproj", "{715CAABD-41C8-4CF4-95FC-204705D2B3E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -25,10 +25,6 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A985BD0A-4AEF-44D6-BD36-5F8035A25ED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A985BD0A-4AEF-44D6-BD36-5F8035A25ED7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A985BD0A-4AEF-44D6-BD36-5F8035A25ED7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A985BD0A-4AEF-44D6-BD36-5F8035A25ED7}.Release|Any CPU.Build.0 = Release|Any CPU
{5A28EF92-805F-44E9-8E13-EA9A5BB623BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A28EF92-805F-44E9-8E13-EA9A5BB623BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A28EF92-805F-44E9-8E13-EA9A5BB623BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -37,6 +33,10 @@ Global
{288943FE-E3E6-49E2-8C6F-9B5B9242266C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{288943FE-E3E6-49E2-8C6F-9B5B9242266C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{288943FE-E3E6-49E2-8C6F-9B5B9242266C}.Release|Any CPU.Build.0 = Release|Any CPU
{715CAABD-41C8-4CF4-95FC-204705D2B3E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{715CAABD-41C8-4CF4-95FC-204705D2B3E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{715CAABD-41C8-4CF4-95FC-204705D2B3E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{715CAABD-41C8-4CF4-95FC-204705D2B3E6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E29A838A-6D2A-4EC6-B2E5-7D53DB491A3F} = {B97A35A0-4616-4B3F-8F73-A66827BC98BA}