Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,80 +15,21 @@ public partial class ExpressionBenchmarks
private static string source = "(((((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))))+(((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))))))))+((((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))))+(((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))))))))) + (((((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))))+(((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))))))))+((((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))))+(((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))) + ((((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))+(((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))) + ((((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1))) + (((2 + 2) + (1 + 1)) + ((2 + 2) + (1 + 1)))))))))";

[Benchmark]
public ParseResult<int> Parse()
public ParseResult<int> ExpressionParser()
{
return new Parser(new Lexer(source)).ParseProgram();
return new ExpressionParser(new Lexer(source)).ParseProgram();
}


[Benchmark]
public List<Token<TokenType>> Lex()
public ParseResult<int> ManualExpressionParser()
{
return new Lexer(source).LexAll();
return new ManualExpressionParser(new Lexer(source)).ParseProgram();
}

}

public enum TokenType
{
[Error] Error,
[End] End,
[Ignore][Regex(Regexes.Whitespace)] Whitespace,

[Token("(")] OpenParen,
[Token(")")] CloseParen,

[Token("+")] Add,
[Token("-")] Sub,
[Token("*")] Mul,
[Token("/")] Div,
[Token("%")] Mod,
[Token("^")] Exp,

[Token(";")] Semicol,

[Regex(Regexes.IntLiteral)] IntLit,
}

[Lexer(typeof(TokenType))]
public partial class Lexer
{
public List<Token<TokenType>> LexAll()
[Benchmark]
public List<Token<TokenType>> Lex()
{
var list = new List<Token<TokenType>>();
while (true)
{
var token = Next();
list.Add(token);
if (token.Kind == TokenType.End) break;
}
return list;
return new Lexer(source).LexAll();
}
}

[Parser(typeof(TokenType))]
public partial class Parser
{
[Rule("program: expression ';'")]
public static int Program(int n, IToken _) => n;

[Right("^")]
[Left("*", "/", "%")]
[Left("+", "-")]
[Rule("expression")]
public static int BinOp(int a, IToken op, int b) => op.Text switch
{
"^" => (int)Math.Pow(a, b),
"*" => a * b,
"/" => a / b,
"%" => a % b,
"+" => a + b,
"-" => a - b,
_ => throw new NotImplementedException(),
};

[Rule("expression : '(' expression ')'")]
public static int Grouping(IToken _1, int n, IToken _2) => n;

[Rule("expression : IntLit")]
public static int IntLit(IToken token) => int.Parse(token.Text);
}
135 changes: 135 additions & 0 deletions Sources/SynKit/Benchmarks/Parser.Benchmarks/ExpressionParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) 2021-2022 Yoakke.
// Licensed under the Apache License, Version 2.0.
// Source repository: https://github.com/LanguageDev/Yoakke

using Yoakke.SynKit.Lexer;
using Yoakke.SynKit.Parser;
using Yoakke.SynKit.Lexer.Attributes;
using Yoakke.SynKit.Parser.Attributes;

namespace Yoakke.SynKit.Parser.Benchmarks;

public enum TokenType
{
[Error] Error,
[End] End,
[Ignore][Regex(Regexes.Whitespace)] Whitespace,

[Token("(")] OpenParen,
[Token(")")] CloseParen,

[Token("+")] Add,
[Token("-")] Sub,
[Token("*")] Mul,
[Token("/")] Div,
[Token("%")] Mod,
[Token("^")] Exp,

[Token(";")] Semicol,

[Regex(Regexes.IntLiteral)] IntLit,
}

[Lexer(typeof(TokenType))]
public partial class Lexer
{
public List<Token<TokenType>> LexAll()
{
var list = new List<Token<TokenType>>();
while (true)
{
var token = Next();
list.Add(token);
if (token.Kind == TokenType.End) break;
}
return list;
}
}

[Parser(typeof(TokenType))]
public partial class ExpressionParser
{
[Rule("program: expression ';'")]
public static int Program(int n, IToken _) => n;

[Right("^")]
[Left("*", "/", "%")]
[Left("+", "-")]
[Rule("expression")]
public static int BinOp(int a, IToken op, int b) => op.Text switch
{
"^" => (int)Math.Pow(a, b),
"*" => a * b,
"/" => a / b,
"%" => a % b,
"+" => a + b,
"-" => a - b,
_ => throw new NotImplementedException(),
};

[Rule("expression : '(' expression ')'")]
public static int Grouping(IToken _1, int n, IToken _2) => n;

[Rule("expression : IntLit")]
public static int IntLit(IToken token) => int.Parse(token.Text);
}

[Parser(typeof(TokenType))]
public partial class ManualExpressionParser
{
[Rule("program: expression ';'")]
public static int Program(int n, IToken _) => n;

[Rule("expression_level1_operator: ('+' | '-')")]
[Rule("expression_level2_operator: ('*' | '/' | '%')")]
[Rule("expression_level3_operator: ('^')")]
public static IToken Level1Operator(IToken op) => op;

[Rule("expression: (expression_level1 (expression_level1_operator expression_level1)*)")]
[Rule("expression_level1: (expression_level2 (expression_level2_operator expression_level2)*)")]
[Rule("expression_level2: (expression_atomic (expression_level3_operator expression_atomic)*)")]
public static int Pop(Punctuated<int, IToken> p)
{
int result = 0;
IToken? lastOp = null;
foreach (var (n, op) in p)
{
if (lastOp is null)
{
result = n;
lastOp = op;
}
else
{
if (op is null)
{
result = BinOp(result, lastOp, n);
}
else
{
result = BinOp(result, lastOp, n);
lastOp = op;
}
}
}

return result;
}

public static int BinOp(int a, IToken op, int b) => op.Text switch
{
"^" => (int)Math.Pow(a, b),
"*" => a * b,
"/" => a / b,
"%" => a % b,
"+" => a + b,
"-" => a - b,
_ => throw new NotImplementedException(),
};

[Rule("expression_atomic : '(' expression ')'")]
public static int Grouping(IToken _1, int n, IToken _2) => n;

[Rule("expression_atomic : IntLit")]
public static int IntLit(IToken token) => int.Parse(token.Text);
}
10 changes: 8 additions & 2 deletions Sources/SynKit/Benchmarks/Parser.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@

using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

if (args.Length == 1 && args[0] == "parser")
{
new Yoakke.SynKit.Parser.Benchmarks.ExpressionBenchmarks().ManualExpressionParser();
}
else
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"Parser.Benchmarks": {
"commandName": "Project"
},
"Parser": {
"commandName": "Project",
"commandLineArgs": "parser"
}
}
}
13 changes: 12 additions & 1 deletion Sources/SynKit/Libraries/Parser/PunctuatedValue.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021-2022 Yoakke.
// Copyright (c) 2021-2022 Yoakke.
// Licensed under the Apache License, Version 2.0.
// Source repository: https://github.com/LanguageDev/Yoakke

Expand Down Expand Up @@ -31,4 +31,15 @@ public PunctuatedValue(TValue element, TPunct? punctuation)
this.Value = element;
this.Punctuation = punctuation;
}

/// <summary>
/// Deconstructs an object into its value and optional punctuation components.
/// </summary>
/// <param name="value">Represents the main value extracted from the object.</param>
/// <param name="punct">Represents an optional punctuation element associated with the value.</param>
public void Deconstruct(out TValue value, out TPunct? punct)
{
value = this.Value;
punct = this.Punctuation!;
}
}
85 changes: 85 additions & 0 deletions Sources/SynKit/Tests/Parser.Tests/ParseErrorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) 2021-2022 Yoakke.
// Licensed under the Apache License, Version 2.0.
// Source repository: https://github.com/LanguageDev/Yoakke

using Xunit;

namespace Yoakke.SynKit.Parser.Tests;

public class ParseErrorTests
{
[Fact]
public void MergeSameError()
{
var firstError = new ParseError("^", null, 12, "expression");
var secondError = new ParseError("^", null, 12, "expression");

var result = firstError | secondError;

Assert.NotNull(result);
Assert.Null(result.Got);
Assert.Equal(12, result.Position);
Assert.Equal(1, result.Elements.Count);
Assert.True(result.Elements.ContainsKey("expression"));
Assert.Equal(1, result.Elements["expression"].Expected.Count);
Assert.True(result.Elements["expression"].Expected.Contains("^"));
}

[Fact]
public void MergeSameErrorDifferentExpectations()
{
var firstError = new ParseError("^", null, 12, "expression");
var secondError = new ParseError("|", null, 12, "expression");

var result = firstError | secondError;

Assert.NotNull(result);
Assert.Null(result.Got);
Assert.Equal(12, result.Position);
Assert.Equal(1, result.Elements.Count);
Assert.True(result.Elements.ContainsKey("expression"));
Assert.Equal("expression", result.Elements["expression"].Context);
Assert.Equal(2, result.Elements["expression"].Expected.Count);
Assert.True(result.Elements["expression"].Expected.Contains("^"));
Assert.True(result.Elements["expression"].Expected.Contains("|"));
}

[Fact]
public void MergeThreeErrors()
{
var firstError = new ParseError("^", null, 12, "expression");
var secondError = new ParseError("|", null, 12, "expression");
var thirdError = new ParseError(":", null, 12, "expression");

var result = firstError | secondError | thirdError;

Assert.NotNull(result);
Assert.Null(result.Got);
Assert.Equal(12, result.Position);
Assert.Equal(1, result.Elements.Count);
Assert.True(result.Elements.ContainsKey("expression"));
Assert.Equal(3, result.Elements["expression"].Expected.Count);
Assert.True(result.Elements["expression"].Expected.Contains("^"));
Assert.True(result.Elements["expression"].Expected.Contains("|"));
Assert.True(result.Elements["expression"].Expected.Contains(":"));
}

[Fact]
public void MergeTwoErrorsFromDifferentExpressions()
{
var firstError = new ParseError("^", null, 12, "expression");
var secondError = new ParseError("^", null, 12, "other_expression");

var result = firstError | secondError;

Assert.NotNull(result);
Assert.Null(result.Got);
Assert.Equal(12, result.Position);
Assert.Equal(2, result.Elements.Count);
Assert.True(result.Elements.ContainsKey("expression"));
Assert.Equal(1, result.Elements["expression"].Expected.Count);
Assert.True(result.Elements["expression"].Expected.Contains("^"));
Assert.Equal(1, result.Elements["other_expression"].Expected.Count);
Assert.True(result.Elements["other_expression"].Expected.Contains("^"));
}
}
Loading