feat: Parser Combinator库和词法分析器 (#2)
All checks were successful
Run unit test / Unit-Test (push) Successful in 41s
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:
parent
57c31ec435
commit
3ed8bf5d36
49
CanonSharp.Combinator/Abstractions/FailedResult.cs
Normal file
49
CanonSharp.Combinator/Abstractions/FailedResult.cs
Normal 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}.";
|
||||
}
|
||||
}
|
26
CanonSharp.Combinator/Abstractions/IReadState.cs
Normal file
26
CanonSharp.Combinator/Abstractions/IReadState.cs
Normal 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; }
|
||||
}
|
48
CanonSharp.Combinator/Abstractions/ParseResult.cs
Normal file
48
CanonSharp.Combinator/Abstractions/ParseResult.cs
Normal 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);
|
||||
}
|
43
CanonSharp.Combinator/Abstractions/Parser.cs
Normal file
43
CanonSharp.Combinator/Abstractions/Parser.cs
Normal 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);
|
||||
}
|
33
CanonSharp.Combinator/Abstractions/SuccessfulResult.cs
Normal file
33
CanonSharp.Combinator/Abstractions/SuccessfulResult.cs
Normal 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;
|
||||
}
|
9
CanonSharp.Combinator/CanonSharp.Combinator.csproj
Normal file
9
CanonSharp.Combinator/CanonSharp.Combinator.csproj
Normal file
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
572
CanonSharp.Combinator/Extensions/ParserExtensions.cs
Normal file
572
CanonSharp.Combinator/Extensions/ParserExtensions.cs
Normal 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
|
||||
}
|
18
CanonSharp.Combinator/Extensions/ReadStateExtensions.cs
Normal file
18
CanonSharp.Combinator/Extensions/ReadStateExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
15
CanonSharp.Combinator/ParseException.cs
Normal file
15
CanonSharp.Combinator/ParseException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
67
CanonSharp.Combinator/ParseResultBuilder.cs
Normal file
67
CanonSharp.Combinator/ParseResultBuilder.cs
Normal 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);
|
||||
}
|
223
CanonSharp.Combinator/ParserBuilder.cs
Normal file
223
CanonSharp.Combinator/ParserBuilder.cs
Normal 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
|
||||
}
|
21
CanonSharp.Combinator/Parsers/Bases/AlternativeParser.cs
Normal file
21
CanonSharp.Combinator/Parsers/Bases/AlternativeParser.cs
Normal 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)));
|
||||
}
|
||||
}
|
20
CanonSharp.Combinator/Parsers/Bases/BindParser.cs
Normal file
20
CanonSharp.Combinator/Parsers/Bases/BindParser.cs
Normal 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));
|
||||
}
|
33
CanonSharp.Combinator/Parsers/Bases/FixParser.cs
Normal file
33
CanonSharp.Combinator/Parsers/Bases/FixParser.cs
Normal 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);
|
||||
}
|
21
CanonSharp.Combinator/Parsers/Bases/MapParser.cs
Normal file
21
CanonSharp.Combinator/Parsers/Bases/MapParser.cs
Normal 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)));
|
||||
}
|
26
CanonSharp.Combinator/Parsers/Bases/NextParser.cs
Normal file
26
CanonSharp.Combinator/Parsers/Bases/NextParser.cs
Normal 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)));
|
||||
}
|
||||
}
|
24
CanonSharp.Combinator/Parsers/Bases/ResumeParser.cs
Normal file
24
CanonSharp.Combinator/Parsers/Bases/ResumeParser.cs
Normal 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)));
|
||||
}
|
||||
}
|
27
CanonSharp.Combinator/Parsers/ModifiedParser.cs
Normal file
27
CanonSharp.Combinator/Parsers/ModifiedParser.cs
Normal 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))));
|
||||
}
|
30
CanonSharp.Combinator/Parsers/Modifiers/DoParser.cs
Normal file
30
CanonSharp.Combinator/Parsers/Modifiers/DoParser.cs
Normal 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;
|
||||
}
|
||||
}
|
21
CanonSharp.Combinator/Parsers/Modifiers/LookAheadParser.cs
Normal file
21
CanonSharp.Combinator/Parsers/Modifiers/LookAheadParser.cs
Normal 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);
|
||||
}
|
26
CanonSharp.Combinator/Parsers/Modifiers/ReverseParser.cs
Normal file
26
CanonSharp.Combinator/Parsers/Modifiers/ReverseParser.cs
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
23
CanonSharp.Combinator/Parsers/Modifiers/TryParser.cs
Normal file
23
CanonSharp.Combinator/Parsers/Modifiers/TryParser.cs
Normal 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);
|
||||
}
|
25
CanonSharp.Combinator/Parsers/PrimitiveParser.cs
Normal file
25
CanonSharp.Combinator/Parsers/PrimitiveParser.cs
Normal 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));
|
||||
}
|
51
CanonSharp.Combinator/Parsers/Primitives/FailedParser.cs
Normal file
51
CanonSharp.Combinator/Parsers/Primitives/FailedParser.cs
Normal 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);
|
||||
}
|
27
CanonSharp.Combinator/Parsers/Primitives/PureParser.cs
Normal file
27
CanonSharp.Combinator/Parsers/Primitives/PureParser.cs
Normal 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);
|
||||
}
|
18
CanonSharp.Combinator/Parsers/Primitives/SatisfyParser.cs
Normal file
18
CanonSharp.Combinator/Parsers/Primitives/SatisfyParser.cs
Normal 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);
|
||||
}
|
||||
}
|
22
CanonSharp.Combinator/Parsers/Primitives/SkipParser.cs
Normal file
22
CanonSharp.Combinator/Parsers/Primitives/SkipParser.cs
Normal 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);
|
||||
}
|
||||
}
|
23
CanonSharp.Combinator/Parsers/Primitives/TakeParser.cs
Normal file
23
CanonSharp.Combinator/Parsers/Primitives/TakeParser.cs
Normal 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);
|
||||
}
|
||||
}
|
22
CanonSharp.Combinator/Results/FailedResultWithError.cs
Normal file
22
CanonSharp.Combinator/Results/FailedResultWithError.cs
Normal 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);
|
||||
}
|
||||
}
|
24
CanonSharp.Combinator/Results/FailedResultWithException.cs
Normal file
24
CanonSharp.Combinator/Results/FailedResultWithException.cs
Normal 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);
|
||||
}
|
24
CanonSharp.Combinator/Results/FailedResultWithMessage.cs
Normal file
24
CanonSharp.Combinator/Results/FailedResultWithMessage.cs
Normal 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);
|
||||
}
|
||||
}
|
23
CanonSharp.Combinator/Results/InternalSuccessfulResult.cs
Normal file
23
CanonSharp.Combinator/Results/InternalSuccessfulResult.cs
Normal 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);
|
||||
}
|
22
CanonSharp.Combinator/Text/StringParser.cs
Normal file
22
CanonSharp.Combinator/Text/StringParser.cs
Normal 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);
|
||||
}
|
||||
}
|
123
CanonSharp.Combinator/Text/TextParserBuilder.cs
Normal file
123
CanonSharp.Combinator/Text/TextParserBuilder.cs
Normal 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");
|
||||
}
|
14
CanonSharp.Combinator/Text/TextParserExtensions.cs
Normal file
14
CanonSharp.Combinator/Text/TextParserExtensions.cs
Normal 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);
|
||||
}
|
23
CanonSharp.Combinator/Unit.cs
Normal file
23
CanonSharp.Combinator/Unit.cs
Normal 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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -11,4 +11,8 @@
|
|||
<None Include="../.editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CanonSharp.Combinator\CanonSharp.Combinator.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
150
CanonSharp.Common/Scanner/LexicalScanner.cs
Normal file
150
CanonSharp.Common/Scanner/LexicalScanner.cs
Normal 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();
|
||||
}
|
||||
}
|
29
CanonSharp.Common/Scanner/LexicalToken.cs
Normal file
29
CanonSharp.Common/Scanner/LexicalToken.cs
Normal 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}'";
|
||||
}
|
62
CanonSharp.Common/Scanner/StringReadState.cs
Normal file
62
CanonSharp.Common/Scanner/StringReadState.cs
Normal 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(),
|
||||
};
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CanonSharp.Common\CanonSharp.Common.csproj" />
|
||||
<ProjectReference Include="..\CanonSharp.Parser\CanonSharp.Parser.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
84
CanonSharp.Tests/CombinatorsTests/BasicParsersTests.cs
Normal file
84
CanonSharp.Tests/CombinatorsTests/BasicParsersTests.cs
Normal 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");
|
||||
}
|
||||
}
|
176
CanonSharp.Tests/CombinatorsTests/CombinatorParserTests.cs
Normal file
176
CanonSharp.Tests/CombinatorsTests/CombinatorParserTests.cs
Normal 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, [], "");
|
||||
}
|
||||
}
|
29
CanonSharp.Tests/CombinatorsTests/LinqTests.cs
Normal file
29
CanonSharp.Tests/CombinatorsTests/LinqTests.cs
Normal 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");
|
||||
}
|
||||
}
|
51
CanonSharp.Tests/CombinatorsTests/ModifierParserTests.cs
Normal file
51
CanonSharp.Tests/CombinatorsTests/ModifierParserTests.cs
Normal 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");
|
||||
}
|
||||
}
|
84
CanonSharp.Tests/CombinatorsTests/PrimitiveParserTests.cs
Normal file
84
CanonSharp.Tests/CombinatorsTests/PrimitiveParserTests.cs
Normal 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");
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
21
CanonSharp.Tests/ReaderTests/StringReadStateTests.cs
Normal file
21
CanonSharp.Tests/ReaderTests/StringReadStateTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
167
CanonSharp.Tests/ScannerTest/LexicalParserTests.cs
Normal file
167
CanonSharp.Tests/ScannerTest/LexicalParserTests.cs
Normal 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);
|
||||
}
|
||||
}
|
177
CanonSharp.Tests/ScannerTest/LexicalTokenParserTest.cs
Normal file
177
CanonSharp.Tests/ScannerTest/LexicalTokenParserTest.cs
Normal 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);
|
||||
}
|
||||
}
|
169
CanonSharp.Tests/TextTests/TextParserTests.cs
Normal file
169
CanonSharp.Tests/TextTests/TextParserTests.cs
Normal 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()));
|
||||
}
|
||||
}
|
39
CanonSharp.Tests/Utils/LexicalTestBase.cs
Normal file
39
CanonSharp.Tests/Utils/LexicalTestBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
43
CanonSharp.Tests/Utils/ParserTestsBase.cs
Normal file
43
CanonSharp.Tests/Utils/ParserTestsBase.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue
Block a user