-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first version (Qiita Advent Calendar 2017)
- Loading branch information
Showing
12 changed files
with
1,169 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using System; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
using static Xunit.Assert; | ||
using static Xunit.AssertEx; | ||
|
||
using static ParserCombinator.CharParsers; | ||
|
||
namespace ParserCombinator.Tests | ||
{ | ||
public class CharParserTest | ||
{ | ||
[Fact] | ||
public void AnyTest() | ||
{ | ||
// Any はつねに成功 | ||
var result = Any(Source.Create("a")); // { IsSuccess: true, Result: 'a' } | ||
result.IsSuccess.IsTrue(); | ||
result.Result.Is('a'); | ||
} | ||
|
||
[Fact] | ||
public void DigitTest() | ||
{ | ||
// 数字だったら成功 | ||
var success = Digit(Source.Create("12a")); // { IsSuccess: true, Result: '1' } | ||
success.IsSuccess.IsTrue(); | ||
success.Result.Is('1'); | ||
|
||
// 数字でなければ失敗 | ||
var failed = Digit(Source.Create("a12")); // { IsSuccess: false, Result: Exception } | ||
failed.IsSuccess.IsFalse(); | ||
Throws(typeof(Exception), AccessToFailedResult(failed)); | ||
} | ||
|
||
[Fact] | ||
public void LiteralTest() | ||
{ | ||
var parser = Literal('a'); | ||
var success = parser(Source.Create("abc")); // { IsSuccess: true, Result: 'a' } | ||
success.IsSuccess.IsTrue(); | ||
success.Result.Is('a'); | ||
var failed = parser(Source.Create("ccc")); // { IsSuccess: false, Result: Exception } | ||
failed.IsSuccess.IsFalse(); | ||
Throws(typeof(Exception), AccessToFailedResult(failed)); | ||
} | ||
|
||
|
||
[Fact] | ||
public void IsTest() | ||
{ | ||
var lowerParser = Is(char.IsLower); // 小文字だけ受け付けるパーサ | ||
var success = lowerParser(Source.Create("abc")); // { IsSuccess: true, Result: 'a' } | ||
var failed = lowerParser(Source.Create("ABC")); // { IsSuccess: false, Result: Exception } | ||
|
||
success.IsSuccess.IsTrue(); | ||
success.Result.Is('a'); | ||
|
||
failed.IsSuccess.IsFalse(); | ||
Throws(typeof(Exception), AccessToFailedResult(failed)); | ||
} | ||
|
||
private static Action AccessToFailedResult<T>(ParseResult<T> result) => () => | ||
{ | ||
var tmp = result.Result; | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.0</TargetFramework> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" /> | ||
<PackageReference Include="xunit" Version="2.2.0" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\ParserCombinator\ParserCombinator.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
using System; | ||
using System.Linq; | ||
using Xunit; | ||
using static Xunit.Assert; | ||
using static ParserCombinator.ParseResultHelper; | ||
using static ParserCombinator.CharParsers; | ||
|
||
namespace ParserCombinator.Tests | ||
{ | ||
public class PostalCodeParserTests | ||
{ | ||
[Fact] | ||
public void SimplePostalCodeParserTest() | ||
{ | ||
// xxx-yyyy の xxx 部分 | ||
Parser<int> leftPart = Digit.Repeat(3).Map(chars => int.Parse(new string(chars.ToArray()))); | ||
|
||
// xxx-yyyy の yyyy 部分 | ||
Parser<int> rightPart = Digit.Repeat(4).Map(chars => int.Parse(new string(chars.ToArray()))); | ||
|
||
// xxx-yyyy の形式の郵便番号のパーサ | ||
Parser<PostalCode> postalCodeParser = leftPart | ||
.Left(Literal('-')) | ||
.Sequence(rightPart, (left, right) => new PostalCode(left, right)); | ||
|
||
ParseResult<PostalCode> result = postalCodeParser(Source.Create("123-4567")); | ||
PostalCode postalCode = result.Result; | ||
|
||
result.IsSuccess.IsTrue(); | ||
postalCode.IsStructuralEqual(new PostalCode(123, 4567)); | ||
} | ||
|
||
[Fact] | ||
public void PostalCodeParserTest() | ||
{ | ||
// xxx-yyyy の xxx 部分 | ||
Parser<int> leftPart = Digit.Repeat(3).Map(chars => int.Parse(new string(chars.ToArray()))); | ||
|
||
// xxx-yyyy の yyyy 部分 | ||
Parser<int> rightPart = Digit.Repeat(4).Map(chars => int.Parse(new string(chars.ToArray()))); | ||
|
||
// 普通の xxx-yyyy | ||
Parser<PostalCode> normal = leftPart.Left(Literal('-')).Sequence(rightPart, (l, r) => new PostalCode(l, r)); | ||
|
||
// xxxyyyy | ||
Parser<PostalCode> withoutSeparator = leftPart.Sequence(rightPart, (l, r) => new PostalCode(l, r)); | ||
|
||
Parser<PostalCode> postalCode = normal.Or(withoutSeparator); | ||
|
||
// 〒 が付加されてもよい | ||
Parser<PostalCode> postalCodeParser = Literal('〒').Right(postalCode).Or(postalCode); | ||
|
||
var expected = new PostalCode(123, 4567); | ||
postalCodeParser(Source.Create("123-4567")).Result.IsStructuralEqual(expected); | ||
postalCodeParser(Source.Create("1234567")).Result.IsStructuralEqual(expected); | ||
postalCodeParser(Source.Create("〒123-4567")).Result.IsStructuralEqual(expected); | ||
postalCodeParser(Source.Create("〒1234567")).Result.IsStructuralEqual(expected); | ||
} | ||
} | ||
|
||
public class PostalCode | ||
{ | ||
public int LeftPart { get; } | ||
|
||
public int RightPart { get; } | ||
|
||
public PostalCode(int left, int right) | ||
{ | ||
this.LeftPart = left; | ||
this.RightPart = right; | ||
} | ||
|
||
public override string ToString() => $"{LeftPart}-{RightPart}"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 2013 | ||
VisualStudioVersion = 12.0.0.0 | ||
MinimumVisualStudioVersion = 10.0.0.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParserCombinator", "ParserCombinator/ParserCombinator.csproj", "{BD5A64D0-B4CE-4130-A43B-26519E0B9665}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParserCombinator.Tests", "ParserCombinator.Tests\ParserCombinator.Tests.csproj", "{1593751F-40C6-44DA-8D0C-B3588A0DBFEE}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{BD5A64D0-B4CE-4130-A43B-26519E0B9665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{BD5A64D0-B4CE-4130-A43B-26519E0B9665}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{BD5A64D0-B4CE-4130-A43B-26519E0B9665}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{BD5A64D0-B4CE-4130-A43B-26519E0B9665}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{1593751F-40C6-44DA-8D0C-B3588A0DBFEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{1593751F-40C6-44DA-8D0C-B3588A0DBFEE}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{1593751F-40C6-44DA-8D0C-B3588A0DBFEE}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{1593751F-40C6-44DA-8D0C-B3588A0DBFEE}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System; | ||
|
||
namespace ParserCombinator | ||
{ | ||
using static ParseResultHelper; | ||
|
||
public static class CharParsers | ||
{ | ||
public static Parser<char> Any { get; } = (Source s) => | ||
{ | ||
var (c, next) = s.Read(); | ||
return Success(next, c); | ||
}; | ||
|
||
public static Parser<char> Digit { get; } = (Source s) => | ||
{ | ||
var (c, next) = s.Read(); | ||
return char.IsDigit(c) ? Success(next, c) : Failed<char>(next, "Is not a digit."); | ||
}; | ||
|
||
public static Parser<char> Literal(char literal) => (Source s) => | ||
{ | ||
var (c, next) = s.Read(); | ||
return c == literal ? Success(next, c) : Failed<char>(next, $"{c} is not equals {literal}"); | ||
}; | ||
|
||
public static Parser<char> Is(Func<char, bool> predicate) => (Source s) => | ||
{ | ||
var (c, next) = s.Read(); | ||
return predicate(c) ? Success(next, c) : Failed<char>(next, $"predicate({c}) returns false."); | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System; | ||
using System.Collections.Immutable; | ||
using System.Net.Mime; | ||
using static ParserCombinator.ParseResultHelper; | ||
|
||
namespace ParserCombinator | ||
{ | ||
public static class Combinators | ||
{ | ||
public static Parser<ImmutableList<T>> Many<T>(this Parser<T> parser) | ||
{ | ||
ParseResult<ImmutableList<T>> Impl(Source s, ImmutableList<T> results) | ||
{ | ||
var result = parser(s); | ||
|
||
return result.IsSuccess | ||
? Impl(result.Source, results.Add(result.Result)) | ||
: Success(s, results); | ||
} | ||
|
||
return (Source s) => Impl(s, ImmutableList<T>.Empty); | ||
} | ||
|
||
public static Parser<ImmutableList<T>> Repeat<T>(this Parser<T> parser, int count) | ||
{ | ||
ParseResult<ImmutableList<T>> Impl(Source s, int c, ImmutableList<T> results) | ||
{ | ||
if (c == 0) | ||
{ | ||
// 0 回を指定されたら終わり | ||
return Success(s, results); | ||
} | ||
|
||
var result = parser(s); | ||
|
||
return result.IsSuccess | ||
? Impl(result.Source, c - 1, results.Add(result.Result)) | ||
: Failed<ImmutableList<T>>(result.Source, result.Reason); | ||
} | ||
|
||
return (Source s) => Impl(s, count, ImmutableList<T>.Empty); | ||
} | ||
|
||
public static Parser<ImmutableList<T>> Sequence<T>(this Parser<T> first, Parser<T> second) => | ||
first.Sequence(second, (f, s) => ImmutableList<T>.Empty.Add(f).Add(s)); | ||
|
||
public static Parser<ImmutableList<T>> Sequence<T>(this Parser<ImmutableList<T>> first, Parser<T> second) => | ||
first.Sequence(second, (f, s) => f.Add(s)); | ||
|
||
public static Parser<TResult> Sequence<TFirst, TSecond, TResult>(this Parser<TFirst> first, Parser<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) => | ||
(Source s) => | ||
{ | ||
var firstResult = first(s); | ||
if (firstResult.IsSuccess) | ||
{ | ||
var secondResult = second(firstResult.Source); | ||
|
||
return secondResult.IsSuccess | ||
? Success(secondResult.Source, resultSelector(firstResult.Result, secondResult.Result)) | ||
: Failed<TResult>(secondResult.Source, secondResult.Reason); | ||
} | ||
else | ||
{ | ||
return Failed<TResult>(firstResult.Source, firstResult.Reason); | ||
} | ||
}; | ||
|
||
public static Parser<T> Or<T>(this Parser<T> left, Parser<T> right) => (Source s) => | ||
{ | ||
var leftResult = left(s); | ||
|
||
return leftResult.IsSuccess | ||
? leftResult | ||
: right(s); | ||
}; | ||
|
||
public static Parser<TLeft> Left<TLeft, TRight>(this Parser<TLeft> left, Parser<TRight> right) => | ||
left.Sequence(right, (l, r) => l); | ||
|
||
public static Parser<TRight> Right<TLeft, TRight>(this Parser<TLeft> left, Parser<TRight> right) => | ||
left.Sequence(right, (l, r) => r); | ||
|
||
public static Parser<TResult> Map<TParser, TResult>(this Parser<TParser> parser, Func<TParser, TResult> mapper) => | ||
(Source s) => | ||
{ | ||
var result = parser(s); | ||
return result.IsSuccess | ||
? Success(result.Source, mapper(result.Result)) | ||
: Failed<TResult>(result.Source, result.Reason); | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using System; | ||
|
||
namespace ParserCombinator | ||
{ | ||
public struct ParseResult<T> | ||
{ | ||
/// <summary>実行後の Source</summary> | ||
public Source Source { get; } | ||
|
||
/// <summary>成功したかどうか</summary> | ||
public bool IsSuccess { get; } | ||
|
||
/// <summary>パース結果</summary> | ||
public T Result => | ||
this.IsSuccess ? _result : throw new Exception($"Parse error: {Reason}"); | ||
|
||
private readonly T _result; | ||
|
||
// 失敗した理由 | ||
public string Reason { get; } | ||
|
||
internal ParseResult(Source source, bool isSuccess, T result, string reason) | ||
{ | ||
this.Source = source; | ||
this.IsSuccess = isSuccess; | ||
_result = result; | ||
this.Reason = reason; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace ParserCombinator | ||
{ | ||
public static class ParseResultHelper | ||
{ | ||
public static ParseResult<T> Success<T>(Source source, T result) | ||
=> new ParseResult<T>(source, true, result, default); | ||
|
||
public static ParseResult<T> Failed<T>(Source source, string reason) | ||
=> new ParseResult<T>(source, false, default, reason); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
namespace ParserCombinator | ||
{ | ||
public delegate ParseResult<T> Parser<T>(Source source); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<LangVersion>latest</LangVersion> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" /> | ||
</ItemGroup> | ||
</Project> |
Oops, something went wrong.