├── .gitattributes ├── Pegasus.Tests ├── TestCases │ ├── simple.txt │ ├── simple.peg │ └── gitter-piratejon.txt ├── Tracing │ ├── tracing-test.txt │ ├── tracing-test.peg │ └── NullTracerTests.cs ├── Properties │ └── AssemblyInfo.cs ├── Expressions │ ├── AndExpressionTests.cs │ ├── NameExpressionTests.cs │ ├── NotExpressionTests.cs │ ├── AndCodeExpressionTests.cs │ ├── NotCodeExpressionTests.cs │ ├── ChoiceExpressionTests.cs │ ├── SequenceExpressionTests.cs │ ├── CodeExpressionTests.cs │ ├── ClassExpressionTests.cs │ ├── PrefixedExpressionTests.cs │ ├── TypedExpressionTests.cs │ ├── RepetitionExpressionTests.cs │ ├── RuleTests.cs │ ├── GrammarTests.cs │ ├── IdentifierTests.cs │ ├── QuantifierTests.cs │ ├── LiteralExpressionTests.cs │ ├── CodeSpanTests.cs │ └── CharacterRangeTests.cs ├── TraceUtility.cs ├── CodeCompileFailedException.cs ├── StringUtilities.cs ├── Common │ └── CacheKeyTests.cs ├── packages.config ├── CultureUtilities.cs ├── CompileManagerTests.cs └── Disposable.cs ├── Pegasus.Workbench ├── Tutorials │ ├── 05 - Calculator.txt │ ├── 01 - Hello world!.txt │ ├── 02 - Variables.txt │ ├── 03 - Code Blocks.txt │ ├── 04 - Class Members.txt │ ├── 06 - Error Handling.txt │ ├── 01 - Hello world!.peg │ ├── 02 - Variables.peg │ ├── 04 - Class Members.peg │ ├── 06 - Error Handling.peg │ ├── 03 - Code Blocks.peg │ └── 05 - Calculator.peg ├── GlobalSuppressions.cs ├── Resources │ ├── Error.png │ ├── Warning.png │ └── Information.png ├── App.config ├── Properties │ ├── Settings.settings │ ├── AssemblyInfo.cs │ └── Settings.Designer.cs ├── App.xaml ├── App.xaml.cs ├── Pipeline │ ├── Model │ │ ├── ExportedRuleEntrypoint.cs │ │ ├── PublicRuleEntrypoint.cs │ │ ├── StartRuleEntrypoint.cs │ │ └── ParserEntrypoint.cs │ └── PegCompiler.cs ├── Commands.cs ├── FileUtilities.cs ├── packages.config └── PathToFileNameConverter.cs ├── Key.snk ├── Pegasus.ico ├── Pegasus.png ├── stylecop.json ├── .nuget └── nuget.exe ├── GitVersion.yaml ├── Pegasus.Templates ├── PegGrammar.ico ├── Parser.peg ├── packages.config ├── SetVersion.msbuild └── PegGrammar.vstemplate ├── Pegasus.Package ├── Resources │ ├── Package.ico │ └── Pegasus.png ├── Properties │ └── AssemblyInfo.cs ├── SetVersion.msbuild ├── NuGetPackage.msbuild ├── GuidList.cs ├── PegasusPackage.cs ├── source.extension.vsixmanifest ├── packages.config ├── Resources.Designer.cs └── Resources.resx ├── Package ├── packages.config ├── Pegasus.nuspec ├── Package.csproj └── Pegasus.targets ├── Pegasus ├── Properties │ └── AssemblyInfo.cs ├── Compiler │ ├── CodeGenerator │ │ ├── ChoiceExpression.weave │ │ ├── _config.weave │ │ ├── WildcardExpression.weave │ │ ├── PrefixedExpression.weave │ │ ├── Code.weave │ │ ├── CodeAssertion.weave │ │ ├── ClassExpression.weave │ │ ├── LiteralExpression.weave │ │ ├── SequenceExpression.weave │ │ ├── NameExpression.weave │ │ ├── CodeExpression.weave │ │ ├── Assertion.weave │ │ ├── Sequence.weave │ │ └── RepetitionExpression.weave │ ├── CompilePass.cs │ ├── ReportNoRulesPass.cs │ ├── ReportUnknownTypesPass.cs │ ├── ReportDuplicateRulesPass.cs │ ├── ReportStartRuleNotFoundPass.cs │ ├── LeftRecursionDetector.cs │ ├── ReportRuleFlagsIssuesPass.cs │ ├── ReportPublicRuleNameIssuesPass.cs │ ├── ReportLeftRecursionPass.cs │ ├── ReportResourcesMissingPass.cs │ ├── SettingName.cs │ ├── ReportMissingRulesPass.cs │ ├── GenerateCodePass.cs │ ├── ReportInvalidQuantifiersPass.cs │ ├── VisibleRules.cs │ ├── PegCompiler.cs │ ├── ReportCodeSyntaxIssuesPass.cs │ ├── ReportConflictingNamesPass.cs │ ├── ReportSettingsIssuesPass.cs │ └── LeftAdjacencyDetector.cs ├── Expressions │ ├── WildcardExpression.cs │ ├── Expression.cs │ ├── AndCodeExpression.cs │ ├── NotCodeExpression.cs │ ├── NameExpression.cs │ ├── AndExpression.cs │ ├── NotExpression.cs │ ├── ChoiceExpression.cs │ ├── PrefixedExpression.cs │ ├── RepetitionExpression.cs │ ├── TypedExpression.cs │ ├── ClassExpression.cs │ ├── Identifier.cs │ ├── Grammar.cs │ ├── CodeSpan.cs │ ├── CodeExpression.cs │ ├── SequenceExpression.cs │ ├── Rule.cs │ ├── Quantifier.cs │ ├── LiteralExpression.cs │ └── CharacterRange.cs ├── Program.cs ├── Parser │ └── CSharpParser.peg └── CompilePegGrammar.cs ├── SharedAssemblyInfo.cs ├── Pegasus.Common ├── Pegasus.Common.ruleset ├── Properties │ └── AssemblyInfo.cs ├── ILexical.cs ├── Highlighting │ ├── HighlightRuleCollection{T}.cs │ ├── HighlightRule{T}.cs │ └── HighlightedSegment{T}.cs ├── IParseResult{T}.cs ├── ParseDelegate.cs ├── LexicalElement.cs ├── ListNode{T}.cs ├── Tracing │ ├── NullTracer.cs │ ├── DiagnosticsTracer.cs │ └── ITracer.cs ├── ListNode.cs ├── Pegasus.Common.csproj └── CacheKey.cs ├── CustomDictionary.xml ├── SharedAssemblyInfo.props ├── .gitignore ├── Tests.ruleset ├── license.md ├── Strict.ruleset ├── appveyor.yml ├── Pegasus.svg └── .editorconfig /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /Pegasus.Tests/TestCases/simple.txt: -------------------------------------------------------------------------------- 1 | OK -------------------------------------------------------------------------------- /Pegasus.Tests/Tracing/tracing-test.txt: -------------------------------------------------------------------------------- 1 | OKOK+OKOKOK -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/05 - Calculator.txt: -------------------------------------------------------------------------------- 1 | 5.1+2*3 -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/01 - Hello world!.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/02 - Variables.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/03 - Code Blocks.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/04 - Class Members.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Key.snk -------------------------------------------------------------------------------- /Pegasus.Tests/TestCases/simple.peg: -------------------------------------------------------------------------------- 1 | start = 'OK' EOF; 2 | EOF = !.; -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/06 - Error Handling.txt: -------------------------------------------------------------------------------- 1 | ((())(()()())) -------------------------------------------------------------------------------- /Pegasus.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.ico -------------------------------------------------------------------------------- /Pegasus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.png -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/stylecop.json -------------------------------------------------------------------------------- /.nuget/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/.nuget/nuget.exe -------------------------------------------------------------------------------- /GitVersion.yaml: -------------------------------------------------------------------------------- 1 | tag-prefix: v 2 | mode: ContinuousDelivery 3 | branches: {} 4 | -------------------------------------------------------------------------------- /Pegasus.Templates/PegGrammar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Templates/PegGrammar.ico -------------------------------------------------------------------------------- /Pegasus.Package/Resources/Package.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Package/Resources/Package.ico -------------------------------------------------------------------------------- /Pegasus.Package/Resources/Pegasus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Package/Resources/Pegasus.png -------------------------------------------------------------------------------- /Pegasus.Workbench/GlobalSuppressions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Workbench/GlobalSuppressions.cs -------------------------------------------------------------------------------- /Pegasus.Workbench/Resources/Error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Workbench/Resources/Error.png -------------------------------------------------------------------------------- /Pegasus.Workbench/Resources/Warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Workbench/Resources/Warning.png -------------------------------------------------------------------------------- /Pegasus.Templates/Parser.peg: -------------------------------------------------------------------------------- 1 | @namespace $rootnamespace$ 2 | @classname $safeitemname$ 3 | @using System.Linq; 4 | 5 | start = 6 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Resources/Information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otac0n/Pegasus/HEAD/Pegasus.Workbench/Resources/Information.png -------------------------------------------------------------------------------- /Package/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Pegasus.Templates/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Pegasus.Workbench/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Pegasus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: Guid("902d3905-d5d6-4c38-bccf-c7095c2be315")] 6 | [assembly: CLSCompliant(true)] 7 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/ChoiceExpression.weave: -------------------------------------------------------------------------------- 1 | @model ChoiceExpression 2 | {{each expression in model.Choices}} 3 | if ({{: this.currentContext.ResultName }} == null) 4 | { 5 | {{@WalkExpression expression}} 6 | } 7 | {{/each}} 8 | -------------------------------------------------------------------------------- /SharedAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System.Reflection; 3 | 4 | [assembly: AssemblyProduct("Pegasus")] 5 | [assembly: AssemblyCopyright("Copyright © 2018 John Gietzen")] 6 | [assembly: System.Runtime.InteropServices.ComVisible(false)] 7 | -------------------------------------------------------------------------------- /Pegasus.Common/Pegasus.Common.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/_config.weave: -------------------------------------------------------------------------------- 1 | @namespace Pegasus.Compiler 2 | @classname CodeGenerator 3 | @encode EscapeName 4 | @static false 5 | @using System.Collections.Generic 6 | @using System.Linq 7 | @using System.Reflection 8 | @using Pegasus.Expressions 9 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/WildcardExpression.weave: -------------------------------------------------------------------------------- 1 | @model WildcardExpression 2 | {{: this.currentContext.ResultName }} = this.ParseAny(ref cursor{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 3 | -------------------------------------------------------------------------------- /CustomDictionary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Memoization 6 | Memoize 7 | Memoized 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SharedAssemblyInfo.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | John Gietzen 4 | Copyright © 2018 $(Authors) 5 | Pegasus 6 | en-US 7 | 8 | 9 | -------------------------------------------------------------------------------- /Pegasus.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Pegasus.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: Guid("cdfb54b1-0863-4e3c-be89-2f98650c1a0e")] 8 | -------------------------------------------------------------------------------- /Pegasus.Tests/Tracing/tracing-test.peg: -------------------------------------------------------------------------------- 1 | @trace true; 2 | start = basicRule leftRecursiveRule outerRule (memoizedRule 'NO' / memoizedRule)*; basicRule = 'OK'; 3 | leftRecursiveRule -memoize = leftRecursiveRule '+' 'OK' / 'OK'; 4 | memoizedRule -memoize = 'OK'; 5 | outerRule = innerRule; 6 | innerRule = 'OK'; 7 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/01 - Hello world!.peg: -------------------------------------------------------------------------------- 1 | // This is a simple "Hello world!" example. 2 | // As you can see from the right hand pane, this 3 | // grammar has successfully parsed the text 4 | // from the "Test" tab. 5 | 6 | greeting 7 | = "Hello world!" EOF 8 | 9 | EOF 10 | = !. 11 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/PrefixedExpression.weave: -------------------------------------------------------------------------------- 1 | @model PrefixedExpression 2 | var {{: model.Prefix.Name + "Start" }} = cursor; 3 | {{@WalkExpression model.Expression}} 4 | var {{: model.Prefix.Name + "End" }} = cursor; 5 | var {{: model.Prefix.Name }} = ValueOrDefault({{: this.currentContext.ResultName }}); 6 | -------------------------------------------------------------------------------- /Pegasus.Package/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Resources; 6 | 7 | [assembly: AssemblyTitle("Pegasus")] 8 | [assembly: AssemblyCulture("")] 9 | [assembly: CLSCompliant(false)] 10 | [assembly: NeutralResourcesLanguage("en-US")] 11 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/02 - Variables.peg: -------------------------------------------------------------------------------- 1 | // Here, we have updated the previous example to 2 | // give the results some structure. 3 | // Note that the curly braces enclose C# code, 4 | // and that 'x' is a C# variable. 5 | 6 | greeting 7 | = x:"Hello, world!" EOF { new { Greeting = x } } 8 | 9 | EOF 10 | = !. 11 | -------------------------------------------------------------------------------- /Pegasus.Common/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using System.Reflection; 4 | 5 | [assembly: AssemblyTitle("Pegasus.Common")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: CLSCompliant(true)] 8 | #if !NETSTANDARD1_0 9 | [assembly: System.Runtime.InteropServices.Guid("c978cd6c-16d4-41f4-b07e-a2fbb786613c")] 10 | #endif 11 | -------------------------------------------------------------------------------- /Pegasus.Workbench/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System.Reflection; 3 | using System.Resources; 4 | using System.Windows; 5 | 6 | [assembly: AssemblyTitle("Pegasus.Workbench")] 7 | [assembly: AssemblyDescription("")] 8 | [assembly: NeutralResourcesLanguage("en-US")] 9 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 10 | -------------------------------------------------------------------------------- /Pegasus/Expressions/WildcardExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | /// 6 | /// Represents a match of any single character. 7 | /// 8 | public class WildcardExpression : Expression 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/Code.weave: -------------------------------------------------------------------------------- 1 | @model object 2 | {{ var span = model as CodeSpan; }} 3 | {{if span != null}} 4 | #line {{= span.Start.Line }} "{{= span.Start.FileName }}"{{ writer.WriteLine(); }}{{= new string(' ', span.Start.Column - 1) }}{{= span.Code.TrimEnd() }} 5 | #line default 6 | {{else}} 7 | {{ var value = model.ToString(); }} 8 | {{if !string.IsNullOrEmpty(value) }} 9 | {{= value }} 10 | {{/if}} 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /Pegasus/Expressions/Expression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | /// 6 | /// Represents an expression that can be matched against a subject. 7 | /// 8 | public abstract class Expression 9 | { 10 | internal Expression() 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/CodeAssertion.weave: -------------------------------------------------------------------------------- 1 | {{ 2 | var mustMatch = (bool)model.MustMatch; 3 | var code = (CodeSpan)model.Code; 4 | }} 5 | if ({{if !mustMatch}}!{{/if}}new Func(state => 6 | {{@RenderCode code}} 7 | )(cursor)) 8 | { 9 | {{: this.currentContext.ResultName }} = this.ReturnHelper(cursor, ref cursor, state => string.Empty{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 10 | } 11 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/ClassExpression.weave: -------------------------------------------------------------------------------- 1 | @model ClassExpression 2 | {{ 3 | var ranges = string.Join(string.Empty, model.Ranges.SelectMany(r => new[] { r.Min, r.Max })); 4 | }} 5 | {{: this.currentContext.ResultName }} = this.ParseClass(ref cursor, {{= ToLiteral(ranges) }}{{if model.Negated}}, negated: true{{/if}}{{if model.IgnoreCase.HasValue}}, ignoreCase: {{= model.IgnoreCase.ToString().ToLower() }}{{/if}}{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 6 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/04 - Class Members.peg: -------------------------------------------------------------------------------- 1 | // Alternatively, we can declare members at the 2 | // top of the file, like so: 3 | 4 | @members 5 | { 6 | private static string ReplaceGreeting(string x) 7 | { 8 | x = x.Replace("a", "i"); 9 | x = x.Replace("e", "i"); 10 | x = x.Replace("o", "i"); 11 | x = x.Replace("u", "i"); 12 | return x; 13 | } 14 | } 15 | 16 | greeting 17 | = x:"Hello, world!" EOF { new { Greeting = ReplaceGreeting(x) } } 18 | 19 | EOF 20 | = !. 21 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/06 - Error Handling.peg: -------------------------------------------------------------------------------- 1 | // This grammar will match any set of balanced 2 | // parentheses and will return a similarly 3 | // nested IList. 4 | // Note the two different ways that error 5 | // expressions have been used to produce 6 | // messages specific to the problem detected. 7 | 8 | subject 9 | = p:prens* EOF { p } 10 | 11 | prens 12 | = "(" p:prens* (")" / #error{ "Expected )" }) { p } 13 | 14 | EOF 15 | = !. 16 | / c:. #error{ "Unexpected character " + c } 17 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CompilePass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | 8 | internal abstract class CompilePass 9 | { 10 | public abstract IList BlockedByErrors { get; } 11 | 12 | public abstract IList ErrorsProduced { get; } 13 | 14 | public abstract void Run(Grammar grammar, CompileResult result); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/AndExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class AndExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullExpression_ThrowsException() 14 | { 15 | Assert.That(() => new AndExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/NameExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class NameExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullIdentifier_ThrowsException() 14 | { 15 | Assert.That(() => new NameExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/NotExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class NotExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullExpression_ThrowsException() 14 | { 15 | Assert.That(() => new NotExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/AndCodeExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class AndCodeExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullCodeSpan_ThrowsException() 14 | { 15 | Assert.That(() => new AndCodeExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/NotCodeExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class NotCodeExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullCodeSpan_ThrowsException() 14 | { 15 | Assert.That(() => new NotCodeExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/ChoiceExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class ChoiceExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullCollectionOfChoices_ThrowsException() 14 | { 15 | Assert.That(() => new ChoiceExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/SequenceExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class SequenceExpressionTests 11 | { 12 | [Test] 13 | public void Constructor_WhenGivenANullSequenceCollection_ThrowsException() 14 | { 15 | Assert.That(() => new SequenceExpression(null), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Workbench/App.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench 4 | { 5 | using System.Windows; 6 | using ICSharpCode.TextEditor.Document; 7 | 8 | /// 9 | /// Interaction logic for the workbench application. 10 | /// 11 | public partial class App : Application 12 | { 13 | private void App_Startup(object sender, StartupEventArgs e) 14 | { 15 | HighlightingManager.Manager.AddHighlightingStrategy(new PegasusHighlightingStrategy()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/CodeExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class CodeExpressionTests 11 | { 12 | [Theory] 13 | public void Constructor_WhenGivenANullCodeSpan_ThrowsException(CodeType codeType) 14 | { 15 | Assert.That(() => new CodeExpression(null, codeType), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Common/ILexical.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | /// 6 | /// Marks a class as being a lexical element. 7 | /// 8 | public interface ILexical 9 | { 10 | /// 11 | /// Gets or sets the ending cursor of this instance. 12 | /// 13 | Cursor EndCursor { get; set; } 14 | 15 | /// 16 | /// Gets or sets the starting cursor of this instance. 17 | /// 18 | Cursor StartCursor { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all */bin/ and */obj/ folders... 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # Ignore backup files. 6 | *.orig 7 | 8 | # Ignore all Visual Studio generated files. 9 | *.sln.ide/ 10 | .vs/ 11 | *.suo 12 | *.user 13 | *.ncb 14 | _ReSharper.* 15 | *.[Cc]ache 16 | _NCrunch*/ 17 | *.ncrunch* 18 | *.docstates 19 | 20 | # Ignore all MonoDevelop generated files. 21 | *.userprefs 22 | *.pidb 23 | 24 | # Ignore all Operating System generated files. 25 | Thumbs.db 26 | 27 | # Ignore all Test Suite status files. 28 | TestResult.xml 29 | *.VisualState.xml 30 | 31 | # Ignore NuGet packages (let package restore handle these) 32 | /packages/ 33 | 34 | # Ignore Weave generated files. 35 | *.weave.cs 36 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/ClassExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Expressions; 8 | 9 | [TestFixture] 10 | public class ClassExpressionTests 11 | { 12 | [Theory] 13 | public void Constructor_WhenGivenANullCollectionOfRanges_ThrowsException(bool negated, bool ignoreCase) 14 | { 15 | Assert.That(() => new ClassExpression(null, negated, ignoreCase), Throws.InstanceOf()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pegasus.Package/SetVersion.msbuild: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Pegasus.Package/NuGetPackage.msbuild: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Packages\Pegasus.$(GitVersion_NuGetVersion).nupkg 10 | true 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/03 - Code Blocks.peg: -------------------------------------------------------------------------------- 1 | // If we want to do something slightly more 2 | // complex in the action, we have several 3 | // options. 4 | // Here is an example of inline C# statements. 5 | // Note the extra set of curly braces. 6 | 7 | @using System.Text.RegularExpressions 8 | 9 | greeting 10 | = x:"Hello, world!" EOF {{ 11 | var greeting = Regex.Replace(x, "[aeou]", "i"); 12 | return new { Greeting = greeting }; 13 | }} 14 | 15 | EOF 16 | = !. 17 | 18 | // Pegasus generates a lambda expression with 19 | // the code you enclose in curly braces. 20 | // In this case, the lambda has a body, like: 21 | // state => { ... } 22 | // Where the previous example was like this: 23 | // state => x 24 | // Both result in valid C#. 25 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/LiteralExpression.weave: -------------------------------------------------------------------------------- 1 | @model LiteralExpression 2 | {{if model.FromResource}} 3 | {{: this.currentContext.ResultName }} = this.ParseLiteral(ref cursor, ParserResources.ResourceManager.GetString({{= ToLiteral(model.Value) }}, ParserResources.Culture){{if model.IgnoreCase.HasValue}}, ignoreCase: {{= model.IgnoreCase.ToString().ToLower() }}{{/if}}{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 4 | {{else}} 5 | {{: this.currentContext.ResultName }} = this.ParseLiteral(ref cursor, {{= ToLiteral(model.Value) }}{{if model.IgnoreCase.HasValue}}, ignoreCase: {{= model.IgnoreCase.ToString().ToLower() }}{{/if}}{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 6 | {{/if}} 7 | -------------------------------------------------------------------------------- /Pegasus.Package/GuidList.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Package 4 | { 5 | using System; 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | internal static class GuidList 9 | { 10 | public const string PegasusCommandSetGuid = "6b605fd3-df12-4868-a1e7-38065862a5c1"; 11 | public const string PegasusPackageGuid = "243c099e-6e07-4be4-a418-84e77bb0f038"; 12 | 13 | [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Required.")] 14 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Required.")] 15 | public static readonly Guid PegasusCommandSet = new Guid(PegasusCommandSetGuid); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Pegasus.Tests/TestCases/gitter-piratejon.txt: -------------------------------------------------------------------------------- 1 | Case when "Service Request".Status IN ('Open', 'In Progress') then (case when Activity."ISP Heat Check"='Dissatisfied' then 'High' else (case when Activity."ISP Heat Check"= 'Neutral' then (case when CEILING(FILTER("- Activity Facts"."# of Activities" USING (Activity."Activity Type" IN ('Call - Inbound', 'Chat', 'Email - Inbound', 'Transfer Accepted'))))>1 and "- Service Request Facts"."Dispatch Activity Count" > 1 then 'High' when CEILING(FILTER("- Activity Facts"."# of Activities" USING (Activity."Activity Type" IN ('Call - Inbound', 'Chat', 'Email - Inbound', 'Transfer Accepted')))) > 1 or "- Service Request Facts"."Dispatch Activity Count" > 1 then 'Med' else 'Low' end) else null end) end) else Null end -------------------------------------------------------------------------------- /Pegasus.Tests/TraceUtility.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests 4 | { 5 | using System; 6 | using System.Diagnostics; 7 | using System.IO; 8 | 9 | internal static class TraceUtility 10 | { 11 | public static string Trace(Action body) 12 | { 13 | var writer = new StringWriter(); 14 | var listener = new TextWriterTraceListener(writer); 15 | 16 | try 17 | { 18 | System.Diagnostics.Trace.Listeners.Add(listener); 19 | body(); 20 | } 21 | finally 22 | { 23 | System.Diagnostics.Trace.Listeners.Remove(listener); 24 | } 25 | 26 | return writer.ToString(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/SequenceExpression.weave: -------------------------------------------------------------------------------- 1 | @model SequenceExpression 2 | {{ 3 | var startCursorName = this.CreateVariable("startCursor"); 4 | var oldContext = this.currentContext; 5 | 6 | var sequence = model.Sequence; 7 | 8 | var codeExpression = sequence.Count > 0 9 | ? sequence[sequence.Count - 1] as CodeExpression 10 | : null; 11 | 12 | if (codeExpression == null || codeExpression.CodeType != CodeType.Result) 13 | { 14 | var newSequence = new List(sequence.Count + 1); 15 | newSequence.AddRange(sequence); 16 | newSequence.Add(null); 17 | sequence = newSequence; 18 | } 19 | }} 20 | var {{: startCursorName }} = cursor; 21 | {{@RenderSequence new { FinalContext = oldContext, Index = 0, Sequence = sequence, StartCursorName = startCursorName } }} 22 | {{ 23 | this.currentContext = oldContext; 24 | }} 25 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportNoRulesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | using Pegasus.Properties; 8 | 9 | internal class ReportNoRulesPass : CompilePass 10 | { 11 | public override IList BlockedByErrors => new string[0]; 12 | 13 | public override IList ErrorsProduced => new[] { "PEG0001" }; 14 | 15 | public override void Run(Grammar grammar, CompileResult result) 16 | { 17 | if (grammar.Rules.Count == 0) 18 | { 19 | var cursor = grammar.End; 20 | result.AddCompilerError(cursor, () => Resources.PEG0001_ERROR_NoRulesDefined); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Package/Pegasus.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pegasus 5 | $version$ 6 | John Gietzen 7 | John Gietzen 8 | https://github.com/otac0n/Pegasus 9 | https://raw.github.com/otac0n/Pegasus/master/Pegasus.ico 10 | Copyright © 2018 John Gietzen 11 | A PEG (Parsing Expression Grammar) parser compiler that integrates with MSBuild / Visual Studio. Features selective packrat parsing and syntax-highlighting output. 12 | parser generator compiler lexer PEG DSL AST 13 | MIT 14 | false 15 | 16 | 17 | -------------------------------------------------------------------------------- /Pegasus.Tests/CodeCompileFailedException.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests 4 | { 5 | using System; 6 | using System.CodeDom.Compiler; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | public class CodeCompileFailedException : Exception 11 | { 12 | public CodeCompileFailedException(IEnumerable errors, IEnumerable messages) 13 | : base("Compile failed:" + Environment.NewLine + StringUtilities.JoinLines(errors.Select(e => e.ToString()))) 14 | { 15 | this.Errors = errors.ToList().AsReadOnly(); 16 | this.Messages = messages.ToList().AsReadOnly(); 17 | } 18 | 19 | public IList Errors { get; } 20 | 21 | public IList Messages { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Pegasus.Common/Highlighting/HighlightRuleCollection{T}.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common.Highlighting 4 | { 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// A list of highlight rules. 9 | /// 10 | /// The type of the value of the match of each of the rules. 11 | public class HighlightRuleCollection : List> 12 | { 13 | /// 14 | /// Adds a rule with the specified pattern and value to the list. 15 | /// 16 | /// The pattern to use for matching. 17 | /// The value of the match. 18 | public void Add(string pattern, T value) => this.Add(new HighlightRule(pattern, value)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Pegasus.Tests/Tracing/NullTracerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Tracing 4 | { 5 | using System.IO; 6 | using NUnit.Framework; 7 | using Pegasus.Common.Tracing; 8 | using Pegasus.Compiler; 9 | using Pegasus.Parser; 10 | 11 | [TestFixture] 12 | public class NullTracerTests 13 | { 14 | [Test] 15 | public void RegressionTest() 16 | { 17 | var grammar = new PegParser().Parse(File.ReadAllText(@"Tracing\tracing-test.peg")); 18 | var compiled = PegCompiler.Compile(grammar); 19 | var parser = CodeCompiler.Compile(compiled); 20 | 21 | parser.Tracer = NullTracer.Instance; 22 | parser.Parse(File.ReadAllText(@"Tracing\tracing-test.txt")); 23 | 24 | Assert.Pass(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pegasus/Expressions/AndCodeExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents an assertion. 9 | /// 10 | public class AndCodeExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The code to execute for the assertion. 16 | public AndCodeExpression(CodeSpan code) 17 | { 18 | this.Code = code ?? throw new ArgumentNullException(nameof(code)); 19 | } 20 | 21 | /// 22 | /// Gets the code expression to be used as an assertion. 23 | /// 24 | public CodeSpan Code { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus/Expressions/NotCodeExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents a negative assertion. 9 | /// 10 | public class NotCodeExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The code to execute for the negative assertion. 16 | public NotCodeExpression(CodeSpan code) 17 | { 18 | this.Code = code ?? throw new ArgumentNullException(nameof(code)); 19 | } 20 | 21 | /// 22 | /// Gets the code expression to be used as an assertion. 23 | /// 24 | public CodeSpan Code { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus.Common/IParseResult{T}.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | /// 6 | /// Encapsulates the success or failure of a particular parsing operation along with the result of operation. 7 | /// 8 | /// The type of the parsing operation's result. 9 | public interface IParseResult 10 | { 11 | /// 12 | /// Gets the ending cursor of the match. 13 | /// 14 | Cursor EndCursor { get; } 15 | 16 | /// 17 | /// Gets the starting cursor of the match. 18 | /// 19 | Cursor StartCursor { get; } 20 | 21 | /// 22 | /// Gets the resulting value of the parsing operation. 23 | /// 24 | T Value { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus.Templates/SetVersion.msbuild: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(IntermediateOutputPath)Templates\ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Pegasus/Expressions/NameExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents a reference to another expression by name. 9 | /// 10 | public class NameExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The name of the referenced expression. 16 | public NameExpression(Identifier identifier) 17 | { 18 | this.Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier)); 19 | } 20 | 21 | /// 22 | /// Gets the name of the referenced expression. 23 | /// 24 | public Identifier Identifier { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus.Common/ParseDelegate.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | /// 8 | /// Attempts a parse at the specified cursor. 9 | /// 10 | /// The type of the parsing operation's result. 11 | /// The cursor that will be updated upon a successful parse. 12 | /// An , if the parse was successful; null otherwise. 13 | [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Since it would be a breaking change, this will not be renamed.")] 14 | [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", Justification = "Necessary for best performance.")] 15 | public delegate IParseResult ParseDelegate(ref Cursor cursor); 16 | } 17 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/NameExpression.weave: -------------------------------------------------------------------------------- 1 | @model NameExpression 2 | {{if this.currentContext.ResultRuleName == null}} 3 | {{: this.currentContext.ResultName }} = this.{{: model.Identifier.Name }}(ref cursor); 4 | {{else}} 5 | {{ 6 | var oldContext = this.currentContext; 7 | var resultName = this.CreateVariable("r"); 8 | this.currentContext = new ResultContext( 9 | resultName: resultName, 10 | resultType: this.currentContext.ResultType); 11 | }} 12 | IParseResult<{{= this.currentContext.ResultType }}> {{: this.currentContext.ResultName }}; 13 | {{@RenderNameExpression model}} 14 | {{ 15 | this.currentContext = oldContext; 16 | }} 17 | {{: this.currentContext.ResultName }} = {{: resultName }} != null 18 | ? this.ReturnHelper<{{= this.currentContext.ResultType }}>({{: resultName }}.StartCursor, ref cursor, state => {{: resultName }}.Value, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}) 19 | : null; 20 | {{/if}} 21 | -------------------------------------------------------------------------------- /Pegasus.Common/LexicalElement.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | using System.Diagnostics; 6 | 7 | /// 8 | /// A basic lexical element class that marks a region of text with a given name. 9 | /// 10 | [DebuggerDisplay("{Name}@{StartCursor.Location}:{EndCursor.Location}")] 11 | public class LexicalElement : ILexical 12 | { 13 | /// 14 | /// Gets or sets the ending cursor of this instance. 15 | /// 16 | public Cursor EndCursor { get; set; } 17 | 18 | /// 19 | /// Gets or sets the name associated with the region of text. 20 | /// 21 | public string Name { get; set; } 22 | 23 | /// 24 | /// Gets or sets the starting cursor of this instance. 25 | /// 26 | public Cursor StartCursor { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/PrefixedExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class PrefixedExpressionTests 12 | { 13 | [Test] 14 | public void Constructor_WhenGivenANullExpression_ThrowsException() 15 | { 16 | var start = new Cursor("OK"); 17 | var end = start.Advance(2); 18 | 19 | Assert.That(() => new PrefixedExpression(new Identifier("OK", start, end), null), Throws.InstanceOf()); 20 | } 21 | 22 | [Test] 23 | public void Constructor_WhenGivenANullPrefix_ThrowsException() 24 | { 25 | Assert.That(() => new PrefixedExpression(null, new WildcardExpression()), Throws.InstanceOf()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pegasus/Expressions/AndExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents a positive look-ahead. 9 | /// 10 | public class AndExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// An expression that must match at a location for this expression to match at that location. 16 | public AndExpression(Expression expression) 17 | { 18 | this.Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 19 | } 20 | 21 | /// 22 | /// Gets the expression that must match at a location for this expression to match at that location. 23 | /// 24 | public Expression Expression { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus/Expressions/NotExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents a negative look-ahead. 9 | /// 10 | public class NotExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// An expression that must not match at a location for this expression to match at that location. 16 | public NotExpression(Expression expression) 17 | { 18 | this.Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 19 | } 20 | 21 | /// 22 | /// Gets the expression that must not match at a location for this expression to match at that location. 23 | /// 24 | public Expression Expression { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Pipeline/Model/ExportedRuleEntrypoint.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench.Pipeline.Model 4 | { 5 | using Pegasus.Common.Tracing; 6 | using Pegasus.Compiler; 7 | using Pegasus.Expressions; 8 | 9 | public sealed class ExportedRuleEntrypoint : ParserEntrypoint 10 | { 11 | private readonly object parser; 12 | 13 | public ExportedRuleEntrypoint(object parser, Rule rule) 14 | : base("Exported." + PublicRuleFinder.GetPublicName(rule), rule) 15 | { 16 | this.parser = parser; 17 | } 18 | 19 | public override object Parse(string subject, string filename, ITracer tracer = null) 20 | { 21 | if (this.parser == null) 22 | { 23 | return null; 24 | } 25 | 26 | this.parser.GetType().GetProperty("Tracer").SetValue(this.parser, tracer); 27 | return "Not supported."; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportUnknownTypesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | using Pegasus.Properties; 8 | 9 | internal class ReportUnknownTypesPass : CompilePass 10 | { 11 | public override IList BlockedByErrors => new[] { "PEG0001", "PEG0002", "PEG0003" }; 12 | 13 | public override IList ErrorsProduced => new[] { "PEG0019" }; 14 | 15 | public override void Run(Grammar grammar, CompileResult result) 16 | { 17 | var types = result.ExpressionTypes; 18 | 19 | foreach (var rule in grammar.Rules) 20 | { 21 | if (!types.ContainsKey(rule.Expression)) 22 | { 23 | result.AddCompilerError(rule.Identifier.Start, () => Resources.PEG0019_ERROR_UnknownType, rule.Identifier.Name); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/TypedExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class TypedExpressionTests 12 | { 13 | [Test] 14 | public void Constructor_WhenGivenANullCodeSpan_ThrowsException() 15 | { 16 | Assert.That(() => new TypedExpression(null, new WildcardExpression()), Throws.InstanceOf()); 17 | } 18 | 19 | [Test] 20 | public void Constructor_WhenGivenANullExpression_ThrowsException() 21 | { 22 | var start = new Cursor("OK"); 23 | var end = start.Advance(2); 24 | var codeSpan = new CodeSpan("OK", start, end); 25 | 26 | Assert.That(() => new TypedExpression(codeSpan, null), Throws.InstanceOf()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Commands.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench 4 | { 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Windows.Input; 7 | using Pegasus.Workbench.Properties; 8 | 9 | /// 10 | /// Contains commands that are available to the application. 11 | /// 12 | public static class Commands 13 | { 14 | /// 15 | /// Gets the "LoadTutorial" command. 16 | /// 17 | [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "This is the intended usage of the RoutedUICommand class.")] 18 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "This is the intended usage of the RoutedUICommand class.")] 19 | public static readonly RoutedUICommand LoadTutorial = new RoutedUICommand(Resources.LoadTutorial, nameof(LoadTutorial), typeof(Commands)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright © 2016 John Gietzen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Pegasus.Templates/PegGrammar.vstemplate: -------------------------------------------------------------------------------- 1 | 2 | 3 | Parser.peg 4 | Pegasus PEG Grammar 5 | A Pegasus grammar describing a recursive descent parser. 6 | CSharp 7 | 10 8 | PegGrammar.ico 9 | 10 | 11 | Parser.peg 12 | 13 | 14 | NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 15 | NuGet.VisualStudio.TemplateWizard 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/RepetitionExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class RepetitionExpressionTests 12 | { 13 | [Test] 14 | public void Constructor_WhenGivenANullExpression_ThrowsException() 15 | { 16 | var start = new Cursor("OK"); 17 | var end = start.Advance(2); 18 | var quantifier = new Quantifier(start, end, 0); 19 | 20 | Assert.That(() => new RepetitionExpression(null, quantifier), Throws.InstanceOf()); 21 | } 22 | 23 | [Test] 24 | public void Constructor_WhenGivenANullQuantifier_ThrowsException() 25 | { 26 | Assert.That(() => new RepetitionExpression(new WildcardExpression(), null), Throws.InstanceOf()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportDuplicateRulesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | using Pegasus.Properties; 8 | 9 | internal class ReportDuplicateRulesPass : CompilePass 10 | { 11 | public override IList BlockedByErrors => new[] { "PEG0001" }; 12 | 13 | public override IList ErrorsProduced => new[] { "PEG0002" }; 14 | 15 | public override void Run(Grammar grammar, CompileResult result) 16 | { 17 | var knownRules = new HashSet(); 18 | 19 | foreach (var rule in grammar.Rules) 20 | { 21 | if (!knownRules.Add(rule.Identifier.Name)) 22 | { 23 | var cursor = rule.Identifier.Start; 24 | result.AddCompilerError(cursor, () => Resources.PEG0002_ERROR_RuleAlreadyDefined, rule.Identifier.Name); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pegasus.Common/ListNode{T}.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | /// 6 | /// Represents a node in a read-only list of . 7 | /// 8 | /// The type of elements in the list. 9 | public class ListNode 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The head of the list. 15 | /// The tail of the list. 16 | public ListNode(T head, ListNode tail = null) 17 | { 18 | this.Head = head; 19 | this.Tail = tail; 20 | } 21 | 22 | /// 23 | /// Gets the head of the list. 24 | /// 25 | public T Head { get; } 26 | 27 | /// 28 | /// Gets the tail of the list. 29 | /// 30 | public ListNode Tail { get; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Tutorials/05 - Calculator.peg: -------------------------------------------------------------------------------- 1 | @using System.Globalization 2 | 3 | // This calculator makes use of Pegasus' "left 4 | // recursion" support to provide the correct 5 | // order of operations. 6 | // Left recursion happens when a rule calls 7 | // itself immediately, before consuming any 8 | // characters. 9 | // Left recursive rules must be memoized. 10 | 11 | start 12 | = value:additive EOF { value } 13 | 14 | additive -memoize 15 | = left:additive "+" right:multiplicative { left + right } 16 | / left:additive "-" right:multiplicative { left - right } 17 | / multiplicative 18 | 19 | multiplicative -memoize 20 | = left:multiplicative "*" right:power { left * right } 21 | / left:multiplicative "/" right:power { left / right } 22 | / power 23 | 24 | power 25 | = left:primary "^" right:power { Math.Pow(left, right) } 26 | / primary 27 | 28 | primary -memoize 29 | = decimal 30 | / "-" primary:primary { -primary } 31 | / "(" additive:additive ")" { additive } 32 | 33 | decimal 34 | = value:([0-9]+ ("." [0-9]+)?) { double.Parse(value, CultureInfo.InvariantCulture) } 35 | 36 | EOF 37 | = !. 38 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Pegasus.Workbench.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pegasus/Expressions/ChoiceExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | /// 10 | /// Represents an ordered choice between a set of expressions. 11 | /// 12 | public class ChoiceExpression : Expression 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The set of expressions to be used as choices. 18 | public ChoiceExpression(IEnumerable choices) 19 | { 20 | if (choices == null) 21 | { 22 | throw new ArgumentNullException(nameof(choices)); 23 | } 24 | 25 | this.Choices = choices.ToList().AsReadOnly(); 26 | } 27 | 28 | /// 29 | /// Gets the ordered set of choices that this expression can match. 30 | /// 31 | public IList Choices { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Strict.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/CodeExpression.weave: -------------------------------------------------------------------------------- 1 | @model CodeExpression 2 | {{if model.CodeType == CodeType.State}} 3 | {{ 4 | var startCursorName = this.CreateVariable("startCursor"); 5 | }} 6 | var {{: startCursorName }} = cursor; 7 | { 8 | var state = cursor.WithMutability(mutable: true); 9 | {{@RenderCode model.CodeSpan}} 10 | cursor = state.WithMutability(mutable: false); 11 | } 12 | {{: this.currentContext.ResultName }} = this.ReturnHelper({{: startCursorName }}, ref cursor, state => null{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 13 | {{elif model.CodeType == CodeType.Error}} 14 | throw this.ExceptionHelper(cursor, state => 15 | {{@RenderCode model.CodeSpan}} 16 | ); 17 | {{elif model.CodeType == CodeType.Parse}} 18 | {{: this.currentContext.ResultName }} = this.ParseHelper<{{= this.currentContext.ResultType }}>(ref cursor, (ref Cursor state) => 19 | {{@RenderCode model.CodeSpan}} 20 | ); 21 | {{else}} 22 | {{ 23 | throw new InvalidOperationException($"Code expressions of type {model.CodeType} are only valid at the end of a sequence expression."); 24 | }} 25 | {{/if}} -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/Assertion.weave: -------------------------------------------------------------------------------- 1 | {{ 2 | var mustMatch = (bool)model.MustMatch; 3 | var expression = (Expression)model.Expression; 4 | var startCursorName = this.CreateVariable("startCursor"); 5 | var oldContext = this.currentContext; 6 | this.currentContext = new ResultContext( 7 | resultName: this.CreateVariable("r"), 8 | resultType: this.types[expression]); 9 | }} 10 | var {{: startCursorName }} = cursor; 11 | IParseResult<{{= this.currentContext.ResultType }}> {{: this.currentContext.ResultName }} = null; 12 | {{@WalkExpression expression}} 13 | if ({{: this.currentContext.ResultName }} {{if mustMatch}}!={{else}}=={{/if}} null) 14 | { 15 | {{if mustMatch}} 16 | cursor = {{: startCursorName }}; 17 | {{/if}} 18 | {{: oldContext.ResultName }} = this.ReturnHelper<{{= oldContext.ResultType }}>(cursor, ref cursor, state => {{if mustMatch}}{{: this.currentContext.ResultName }}.Value{{else}}string.Empty{{/if}}{{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 19 | } 20 | {{if !mustMatch}} 21 | else 22 | { 23 | cursor = {{: startCursorName }}; 24 | } 25 | {{/if}} 26 | {{ 27 | this.currentContext = oldContext; 28 | }} 29 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Pipeline/Model/PublicRuleEntrypoint.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench.Pipeline.Model 4 | { 5 | using System.Reflection; 6 | using Pegasus.Common.Tracing; 7 | using Pegasus.Compiler; 8 | using Pegasus.Expressions; 9 | 10 | public sealed class PublicRuleEntrypoint : ParserEntrypoint 11 | { 12 | private readonly MethodInfo methodInfo; 13 | private readonly object parser; 14 | 15 | public PublicRuleEntrypoint(object parser, Rule rule) 16 | : base("Parse" + PublicRuleFinder.GetPublicName(rule), rule) 17 | { 18 | this.parser = parser; 19 | this.methodInfo = parser?.GetType().GetMethod(this.Name); 20 | } 21 | 22 | public override object Parse(string subject, string filename, ITracer tracer = null) 23 | { 24 | if (this.parser == null) 25 | { 26 | return null; 27 | } 28 | 29 | this.parser.GetType().GetProperty("Tracer").SetValue(this.parser, tracer); 30 | return this.methodInfo.Invoke(this.parser, new object[] { subject, filename }); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportStartRuleNotFoundPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | using Pegasus.Properties; 9 | 10 | internal class ReportStartRuleNotFoundPass : CompilePass 11 | { 12 | public override IList BlockedByErrors => new[] { "PEG0001" }; 13 | 14 | public override IList ErrorsProduced => new[] { "PEG0003" }; 15 | 16 | public override void Run(Grammar grammar, CompileResult result) 17 | { 18 | var knownRules = new HashSet(grammar.Rules.Select(r => r.Identifier.Name)); 19 | 20 | foreach (var setting in grammar.Settings) 21 | { 22 | if (setting.Key.Name == SettingName.Start) 23 | { 24 | var name = setting.Value.ToString().Trim(); 25 | if (!knownRules.Contains(name)) 26 | { 27 | result.AddCompilerError(setting.Key.Start, () => Resources.PEG0003_ERROR_RuleDoesNotExist, name); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Pegasus/Compiler/LeftRecursionDetector.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | 9 | /// 10 | /// Provides left-recursion detection services for Pegasus Grammars. 11 | /// 12 | public static class LeftRecursionDetector 13 | { 14 | /// 15 | /// Detects which rules in a are left-recursive. 16 | /// 17 | /// The left-adjacent expressions to inspect. 18 | /// A containing the left-recursive rules. 19 | /// This does not detect mutual left-recursion. 20 | public static HashSet Detect(ILookup leftAdjacentExpressions) 21 | { 22 | return new HashSet(from i in leftAdjacentExpressions 23 | where i.OfType().Where(n => n.Identifier.Name == i.Key.Identifier.Name).Any() 24 | select i.Key); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pegasus.Workbench/FileUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench 4 | { 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | public static class FileUtilities 10 | { 11 | private const int BufferSize = 4096; 12 | 13 | public static async Task ReadAllTextAsync(string path) 14 | { 15 | using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: BufferSize, useAsync: true)) 16 | using (var reader = new StreamReader(stream, Encoding.Default, detectEncodingFromByteOrderMarks: true, bufferSize: BufferSize, leaveOpen: true)) 17 | { 18 | return await reader.ReadToEndAsync(); 19 | } 20 | } 21 | 22 | public static async Task WriteAllTextAsync(string path, string contents) 23 | { 24 | using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: BufferSize, useAsync: true)) 25 | { 26 | var encoded = Encoding.Default.GetBytes(contents); 27 | await stream.WriteAsync(encoded, 0, encoded.Length); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportRuleFlagsIssuesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | using Pegasus.Properties; 8 | 9 | internal class ReportRuleFlagsIssuesPass : CompilePass 10 | { 11 | private static readonly HashSet KnownFlags = new HashSet 12 | { 13 | "memoize", 14 | "lexical", 15 | "public", 16 | "export", 17 | }; 18 | 19 | public override IList BlockedByErrors => new[] { "PEG0001" }; 20 | 21 | public override IList ErrorsProduced => new[] { "PEG0013" }; 22 | 23 | public override void Run(Grammar grammar, CompileResult result) 24 | { 25 | foreach (var rule in grammar.Rules) 26 | { 27 | foreach (var flag in rule.Flags) 28 | { 29 | if (!KnownFlags.Contains(flag.Name)) 30 | { 31 | var cursor = flag.Start; 32 | result.AddCompilerError(cursor, () => Resources.PEG0013_WARNING_FlagUnknown, flag.Name); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Pegasus/Expressions/PrefixedExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents an expression that has been given a name as a prefix. 9 | /// 10 | public class PrefixedExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The name given to this expression as a prefix. 16 | /// The expression that has been prefixed. 17 | public PrefixedExpression(Identifier prefix, Expression expression) 18 | { 19 | this.Prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); 20 | this.Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 21 | } 22 | 23 | /// 24 | /// Gets the expression that has been prefixed. 25 | /// 26 | public Expression Expression { get; } 27 | 28 | /// 29 | /// Gets the name given to this expression as a prefix. 30 | /// 31 | public Identifier Prefix { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Pegasus.Workbench/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/RuleTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class RuleTests 12 | { 13 | [Test] 14 | public void Constructor_WhenGivenANullExpression_ThrowsException() 15 | { 16 | var start = new Cursor("OK"); 17 | var end = start.Advance(2); 18 | 19 | Assert.That(() => new Rule(new Identifier("OK", start, end), null, new Identifier[0]), Throws.InstanceOf()); 20 | } 21 | 22 | [Test] 23 | public void Constructor_WhenGivenANullFlagsCollection_DoesNotThrow() 24 | { 25 | var start = new Cursor("OK"); 26 | var end = start.Advance(2); 27 | 28 | Assert.That(() => new Rule(new Identifier("OK", start, end), new WildcardExpression(), null), Throws.Nothing); 29 | } 30 | 31 | [Test] 32 | public void Constructor_WhenGivenANullIdentifier_ThrowsException() 33 | { 34 | Assert.That(() => new Rule(null, new WildcardExpression(), new Identifier[0]), Throws.InstanceOf()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pegasus.Common/Tracing/NullTracer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common.Tracing 4 | { 5 | /// 6 | /// Implements a that does nothing. 7 | /// 8 | public class NullTracer : ITracer 9 | { 10 | private NullTracer() 11 | { 12 | } 13 | 14 | /// 15 | /// Gets the instance of . 16 | /// 17 | public static NullTracer Instance { get; } = new NullTracer(); 18 | 19 | /// 20 | public void TraceCacheHit(string ruleName, Cursor cursor, CacheKey cacheKey, IParseResult parseResult) 21 | { 22 | } 23 | 24 | /// 25 | public void TraceCacheMiss(string ruleName, Cursor cursor, CacheKey cacheKey) 26 | { 27 | } 28 | 29 | /// 30 | public void TraceInfo(string ruleName, Cursor cursor, string info) 31 | { 32 | } 33 | 34 | /// 35 | public void TraceRuleEnter(string ruleName, Cursor cursor) 36 | { 37 | } 38 | 39 | /// 40 | public void TraceRuleExit(string ruleName, Cursor cursor, IParseResult parseResult) 41 | { 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/GrammarTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using NUnit.Framework; 8 | using Pegasus.Common; 9 | using Pegasus.Expressions; 10 | 11 | [TestFixture] 12 | public class GrammarTests 13 | { 14 | [Test] 15 | public void Constructor_WhenGivenANullCollectionOfRules_ThrowsException() 16 | { 17 | var start = new Cursor("OK"); 18 | var end = start.Advance(2); 19 | 20 | Assert.That(() => new Grammar(null, new Dictionary(), end), Throws.InstanceOf()); 21 | } 22 | 23 | [Test] 24 | public void Constructor_WhenGivenANullEndCursor_ThrowsException() 25 | { 26 | Assert.That(() => new Grammar(new Rule[0], new Dictionary(), null), Throws.InstanceOf()); 27 | } 28 | 29 | [Test] 30 | public void Constructor_WhenGivenANullSettingsCollection_DoesNotThrow() 31 | { 32 | var start = new Cursor("OK"); 33 | var end = start.Advance(2); 34 | 35 | Assert.That(() => new Grammar(new Rule[0], null, end), Throws.Nothing); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/IdentifierTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class IdentifierTests 12 | { 13 | [Test] 14 | public void Constructor_WhenGivenANullEndCursor_ThrowsException() 15 | { 16 | var start = new Cursor("OK"); 17 | var end = start.Advance(2); 18 | 19 | Assert.That(() => new Identifier("OK", start, null), Throws.InstanceOf()); 20 | } 21 | 22 | [Test] 23 | public void Constructor_WhenGivenANullExpression_ThrowsException() 24 | { 25 | var start = new Cursor("OK"); 26 | var end = start.Advance(2); 27 | 28 | Assert.That(() => new Identifier(null, start, end), Throws.InstanceOf()); 29 | } 30 | 31 | [Test] 32 | public void Constructor_WhenGivenANullStartCursor_ThrowsException() 33 | { 34 | var start = new Cursor("OK"); 35 | var end = start.Advance(2); 36 | 37 | Assert.That(() => new Identifier("OK", null, end), Throws.InstanceOf()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportPublicRuleNameIssuesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | using Pegasus.Properties; 9 | 10 | internal class ReportPublicRuleNameIssuesPass : CompilePass 11 | { 12 | public override IList BlockedByErrors => new string[0]; 13 | 14 | public override IList ErrorsProduced => new[] { "PEG0025" }; 15 | 16 | public override void Run(Grammar grammar, CompileResult result) 17 | { 18 | foreach (var rule in grammar.Rules) 19 | { 20 | if (char.IsLower(rule.Identifier.Name[0])) 21 | { 22 | if (rule.Flags.Any(f => f.Name == "public")) 23 | { 24 | result.AddCompilerError(rule.Identifier.Start, () => Resources.PEG0025_WARNING_LowercasePublicRule, rule.Identifier.Name); 25 | } 26 | else if (rule.Flags.Any(f => f.Name == "export")) 27 | { 28 | result.AddCompilerError(rule.Identifier.Start, () => Resources.PEG0025_WARNING_LowercaseExportedRule, rule.Identifier.Name); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Pegasus.Common/Highlighting/HighlightRule{T}.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common.Highlighting 4 | { 5 | using System.Text.RegularExpressions; 6 | 7 | /// 8 | /// Represents a rule for highlighting. 9 | /// 10 | /// The type of the value of the match. 11 | public class HighlightRule 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The pattern to use for matching. 17 | /// The value of the match. 18 | public HighlightRule(string pattern, T value) 19 | { 20 | var options = 21 | RegexOptions.IgnorePatternWhitespace 22 | #if !NETSTANDARD1_0 23 | | RegexOptions.Compiled 24 | #endif 25 | ; 26 | 27 | this.Pattern = new Regex(pattern, options); 28 | this.Value = value; 29 | } 30 | 31 | /// 32 | /// Gets the pattern to use for matching. 33 | /// 34 | public Regex Pattern { get; } 35 | 36 | /// 37 | /// Gets the value of the match. 38 | /// 39 | public T Value { get; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Pegasus/Expressions/RepetitionExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents the repetition of an expression. 9 | /// 10 | public class RepetitionExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The expression to be repeatedly matched. 16 | /// The quantifier that specifies how many times to match and the delimiter of the matches. 17 | public RepetitionExpression(Expression expression, Quantifier quantifier) 18 | { 19 | this.Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 20 | this.Quantifier = quantifier ?? throw new ArgumentNullException(nameof(quantifier)); 21 | } 22 | 23 | /// 24 | /// Gets the expression to be repeatedly matched. 25 | /// 26 | public Expression Expression { get; } 27 | 28 | /// 29 | /// Gets the quantifier that specifies how many times to match and the delimiter of the matches. 30 | /// 31 | public Quantifier Quantifier { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Pegasus.Tests/StringUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | 12 | internal static class StringUtilities 13 | { 14 | public static string JoinLines(params string[] lines) 15 | { 16 | return JoinLines(lines.AsEnumerable()); 17 | } 18 | 19 | public static string JoinLines(IEnumerable lines) 20 | { 21 | var sb = new StringBuilder(); 22 | foreach (var line in lines) 23 | { 24 | sb.AppendLine(line); 25 | } 26 | 27 | return sb.ToString(); 28 | } 29 | 30 | public static string[] SplitLines(this string lines) => lines.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); 31 | 32 | public static string GetResourceString(this Assembly assembly, string resourceName) 33 | { 34 | var fullName = $"{assembly.GetName().Name}.{resourceName}"; 35 | using (var stream = assembly.GetManifestResourceStream(fullName)) 36 | using (var reader = new StreamReader(stream, Encoding.Default, true, 1024, true)) 37 | { 38 | return reader.ReadToEnd(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Pipeline/Model/StartRuleEntrypoint.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench.Pipeline.Model 4 | { 5 | using Pegasus.Common.Tracing; 6 | using Pegasus.Expressions; 7 | 8 | /// 9 | /// Represents a starting rule, exposed via that Parse method. 10 | /// 11 | public sealed class StartRuleEntrypoint : ParserEntrypoint 12 | { 13 | private readonly object parser; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The parser. 19 | /// The starting rule. 20 | public StartRuleEntrypoint(object parser, Rule startRule) 21 | : base("Parse", startRule) 22 | { 23 | this.parser = parser; 24 | } 25 | 26 | /// 27 | public override object Parse(string subject, string filename, ITracer tracer = null) 28 | { 29 | if (this.parser == null) 30 | { 31 | return null; 32 | } 33 | 34 | var type = this.parser.GetType(); 35 | type.GetProperty("Tracer").SetValue(this.parser, tracer); 36 | return type.GetMethod("Parse").Invoke(this.parser, new object[] { subject, filename }); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Pegasus/Expressions/TypedExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents an expression with a specific type. 9 | /// 10 | public class TypedExpression : Expression 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The specific type of the wrapped expression. 16 | /// The wrapped expression. 17 | public TypedExpression(CodeSpan type, Expression expression) 18 | { 19 | this.Type = type ?? throw new ArgumentNullException(nameof(type)); 20 | this.Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 21 | } 22 | 23 | /// 24 | /// Gets the wrapped expression. 25 | /// 26 | public Expression Expression { get; } 27 | 28 | /// 29 | /// Gets the specific type of the wrapped expression. 30 | /// 31 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Since 'GetType' is defined on the root object type, this is not a confusing usage.")] 32 | public CodeSpan Type { get; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Pegasus/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus 4 | { 5 | using System; 6 | using System.CodeDom.Compiler; 7 | using System.Collections.Generic; 8 | using Pegasus.Properties; 9 | 10 | internal class Program 11 | { 12 | public static int Main(string[] args) 13 | { 14 | if (args.Length == 0) 15 | { 16 | ShowUsage(); 17 | return -1; 18 | } 19 | 20 | var errorCount = 0; 21 | foreach (var arg in args) 22 | { 23 | var errors = new List(); 24 | CompileManager.CompileFile(arg, null, errors.Add); 25 | ShowErrors(errors); 26 | errorCount += errors.Count; 27 | } 28 | 29 | return errorCount; 30 | } 31 | 32 | private static void ShowErrors(List errors) 33 | { 34 | var startingColor = Console.ForegroundColor; 35 | 36 | foreach (var e in errors) 37 | { 38 | Console.ForegroundColor = e.IsWarning ? ConsoleColor.Yellow : ConsoleColor.Red; 39 | Console.WriteLine(e); 40 | } 41 | 42 | Console.ForegroundColor = startingColor; 43 | } 44 | 45 | private static void ShowUsage() 46 | { 47 | Console.WriteLine(Resources.Usage); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportLeftRecursionPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | using Pegasus.Properties; 9 | 10 | internal class ReportLeftRecursionPass : CompilePass 11 | { 12 | public override IList BlockedByErrors => new[] { "PEG0001", "PEG0002", "PEG0003" }; 13 | 14 | public override IList ErrorsProduced => new[] { "PEG0020", "PEG0023" }; 15 | 16 | public override void Run(Grammar grammar, CompileResult result) 17 | { 18 | foreach (var rule in result.LeftRecursiveRules) 19 | { 20 | if (!rule.Flags.Any(f => f.Name == "memoize")) 21 | { 22 | result.AddCompilerError(rule.Identifier.Start, () => Resources.PEG0020_ERROR_UnmemoizedLeftRecursion, rule.Identifier.Name); 23 | } 24 | } 25 | 26 | foreach (var set in result.MutuallyRecursiveRules) 27 | { 28 | var ruleNames = string.Join(", ", set.Select(r => r.Identifier.Name)); 29 | foreach (var rule in set) 30 | { 31 | result.AddCompilerError(rule.Identifier.Start, () => Resources.PEG0023_ERROR_AmbiguousLeftRecursionDetected, rule.Identifier.Name, ruleNames); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/QuantifierTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class QuantifierTests 12 | { 13 | [Datapoints] 14 | public readonly int[] Int32Datapoints = { 0, 1, 10 }; 15 | 16 | [Datapoints] 17 | public readonly int?[] NullableInt32Datapoints = { null, 0, 1, 10 }; 18 | 19 | [Theory] 20 | public void Constructor_WhenGivenANullEndCursor_ThrowsException(int min, int? max, bool nullDelimiter) 21 | { 22 | var start = new Cursor("OK"); 23 | var end = start.Advance(2); 24 | var delimiter = nullDelimiter ? null : new WildcardExpression(); 25 | 26 | Assert.That(() => new Quantifier(start, null, min, max, delimiter), Throws.InstanceOf()); 27 | } 28 | 29 | [Theory] 30 | public void Constructor_WhenGivenANullStartCursor_ThrowsException(int min, int? max, bool nullDelimiter) 31 | { 32 | var start = new Cursor("OK"); 33 | var end = start.Advance(2); 34 | var delimiter = nullDelimiter ? null : new WildcardExpression(); 35 | 36 | Assert.That(() => new Quantifier(null, end, min, max, delimiter), Throws.InstanceOf()); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Pegasus.Common/Highlighting/HighlightedSegment{T}.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common.Highlighting 4 | { 5 | using System.Diagnostics; 6 | 7 | /// 8 | /// Represents a segment of text that is highlighted with an object of type . 9 | /// 10 | /// The type of the value of the highlighted segment. 11 | [DebuggerDisplay("[{Start}, {End}) {Value}")] 12 | public class HighlightedSegment 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The starting index of the segment. 18 | /// The ending index of the segment. 19 | /// The value of the segment. 20 | public HighlightedSegment(int start, int end, T value) 21 | { 22 | this.Start = start; 23 | this.End = end; 24 | this.Value = value; 25 | } 26 | 27 | /// 28 | /// Gets the ending index of the segment. 29 | /// 30 | public int End { get; } 31 | 32 | /// 33 | /// Gets the starting index of the segment. 34 | /// 35 | public int Start { get; } 36 | 37 | /// 38 | /// Gets the value of the segment. 39 | /// 40 | public T Value { get; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/LiteralExpressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class LiteralExpressionTests 12 | { 13 | [Theory] 14 | public void Constructor_WhenGivenANullEndCursor_ThrowsException(bool ignoreCase, bool fromResource) 15 | { 16 | var start = new Cursor("OK"); 17 | var end = start.Advance(2); 18 | 19 | Assert.That(() => new LiteralExpression(start, null, "OK", ignoreCase, fromResource), Throws.InstanceOf()); 20 | } 21 | 22 | [Theory] 23 | public void Constructor_WhenGivenANullStartCursor_ThrowsException(bool ignoreCase, bool fromResource) 24 | { 25 | var start = new Cursor("OK"); 26 | var end = start.Advance(2); 27 | 28 | Assert.That(() => new LiteralExpression(null, end, "OK", ignoreCase, fromResource), Throws.InstanceOf()); 29 | } 30 | 31 | [Theory] 32 | public void Constructor_WhenGivenANullValue_ThrowsException(bool ignoreCase, bool fromResource) 33 | { 34 | var start = new Cursor("OK"); 35 | var end = start.Advance(2); 36 | 37 | Assert.That(() => new LiteralExpression(start, end, null, ignoreCase, fromResource), Throws.InstanceOf()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2017 2 | configuration: Release 3 | install: 4 | - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;C:\\ProgramData\\chocolatey\\bin;%PATH%" 5 | - choco install gitversion.portable -y 6 | - gitversion /l console /output buildserver 7 | - choco install GitReleaseNotes.Portable 8 | - choco install codecov 9 | before_build: 10 | - nuget restore 11 | #- GitReleaseNotes . /AllTags /C breaking /O ReleaseNotes.md 12 | build: 13 | project: Pegasus.sln 14 | after_test: 15 | - .\packages\OpenCover.4.6.519\tools\OpenCover.Console.exe -register:user -target:"nunit-console.exe" -targetargs:".\Pegasus.Tests\bin\%CONFIGURATION%\Pegasus.Tests.dll /noshadow /exclude=Performance" -filter:"+[Pegasus]* +[Pegasus.Common]* -[Pegasus]Pegasus.Program.* -[Pegasus]Pegasus.Parser.* -[Pegasus]Pegasus.Properties.*" -hideskipped:All -output:.\Pegasus.Tests\bin\%CONFIGURATION%\coverage.xml 16 | - codecov -f ".\Pegasus.Tests\bin\%CONFIGURATION%\coverage.xml" 17 | cache: 18 | - packages -> **\packages.config 19 | artifacts: 20 | #- path: 'ReleaseNotes.md' 21 | - path: 'Package\bin\**\*.nupkg' 22 | - path: 'Pegasus.Templates\bin\**\*.zip' 23 | - path: 'Pegasus.Package\bin\**\*.vsix' 24 | - path: 'Pegasus.Workbench\bin\%CONFIGURATION%' 25 | name: Pegasus.Workbench 26 | type: zip 27 | deploy: 28 | provider: NuGet 29 | api_key: 30 | secure: TVpGH+UM7lK2qC2RiNlBY2kLk5MfE228uzlXb1JSaSW05USy6rjkTW+4CksCOu8H 31 | skip_symbols: false 32 | artifact: /.*\.nupkg/ 33 | skip_commits: 34 | files: 35 | - .gitattributes 36 | - .gitignore 37 | - CodeMaid.config 38 | - license.md 39 | - pegasus.gv 40 | - Pegasus.svg 41 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Pipeline/Model/ParserEntrypoint.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench.Pipeline.Model 4 | { 5 | using Pegasus.Common.Tracing; 6 | using Pegasus.Expressions; 7 | 8 | /// 9 | /// The base class for parser entrypoints. 10 | /// 11 | public abstract class ParserEntrypoint 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The name of the entrypoint. 17 | /// The rule implementing the entrypoint. 18 | public ParserEntrypoint(string name, Rule rule) 19 | { 20 | this.Name = name; 21 | this.Rule = rule; 22 | } 23 | 24 | /// 25 | /// Gets the name of the entrypoint. 26 | /// 27 | public string Name { get; } 28 | 29 | /// 30 | /// Gets the rule implementing the entrypoint. 31 | /// 32 | public Rule Rule { get; } 33 | 34 | /// 35 | /// Parse the specified subject. 36 | /// 37 | /// The subject to parse. 38 | /// The filename of the subject. 39 | /// The tracer to use. 40 | /// The parse result. 41 | public abstract object Parse(string subject, string filename, ITracer tracer = null); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Pegasus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 37 | 38 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportResourcesMissingPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | using Pegasus.Properties; 9 | 10 | internal class ReportResourcesMissingPass : CompilePass 11 | { 12 | public override IList BlockedByErrors => new[] { "PEG0001" }; 13 | 14 | public override IList ErrorsProduced => new[] { "PEG0016" }; 15 | 16 | public override void Run(Grammar grammar, CompileResult result) => new MissingRuleExpressionTreeWalker(result).WalkGrammar(grammar); 17 | 18 | private class MissingRuleExpressionTreeWalker : ExpressionTreeWalker 19 | { 20 | private readonly CompileResult result; 21 | 22 | public MissingRuleExpressionTreeWalker(CompileResult result) 23 | { 24 | this.result = result; 25 | } 26 | 27 | public override void WalkGrammar(Grammar grammar) 28 | { 29 | if (!grammar.Settings.Any(s => s.Key.Name == SettingName.Resources)) 30 | { 31 | base.WalkGrammar(grammar); 32 | } 33 | } 34 | 35 | protected override void WalkLiteralExpression(LiteralExpression literalExpression) 36 | { 37 | if (literalExpression.FromResource) 38 | { 39 | this.result.AddCompilerError(literalExpression.Start, () => Resources.PEG0016_ERROR_ResourcesNotSpecified); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Pegasus/Compiler/SettingName.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | /// 6 | /// Contains constants with the supported setting names. 7 | /// 8 | public static class SettingName 9 | { 10 | /// 11 | /// The "accessibility" setting. 12 | /// 13 | public const string Accessibility = "accessibility"; 14 | 15 | /// 16 | /// The "classname" setting. 17 | /// 18 | public const string ClassName = "classname"; 19 | 20 | /// 21 | /// The "ignorecase" setting. 22 | /// 23 | public const string IgnoreCase = "ignorecase"; 24 | 25 | /// 26 | /// The "members" setting. 27 | /// 28 | public const string Members = "members"; 29 | 30 | /// 31 | /// The "namespace" setting. 32 | /// 33 | public const string Namespace = "namespace"; 34 | 35 | /// 36 | /// The "resources" setting. 37 | /// 38 | public const string Resources = "resources"; 39 | 40 | /// 41 | /// The "start" setting. 42 | /// 43 | public const string Start = "start"; 44 | 45 | /// 46 | /// The "trace" setting. 47 | /// 48 | public const string Trace = "trace"; 49 | 50 | /// 51 | /// The "using" setting. 52 | /// 53 | public const string Using = "using"; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Pegasus.Tests/Common/CacheKeyTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Common 4 | { 5 | using NUnit.Framework; 6 | using Pegasus.Common; 7 | 8 | [TestFixture] 9 | public class CacheKeyTests 10 | { 11 | [Test] 12 | public void Equals_WithDifferentCacheKeys_ReturnsFalse() 13 | { 14 | var subjectA = new CacheKey("OK", 0, 0); 15 | var subjectB = new CacheKey("OK", 1, 0); 16 | 17 | Assert.That(subjectA.Equals(subjectB), Is.False); 18 | } 19 | 20 | [Test] 21 | public void Equals_WithIdenticalCacheKeys_ReturnsTrue([Values(0, 1, 2)] int stateKey, [Values(0, 1, 2)] int location) 22 | { 23 | var subjectA = new CacheKey("OK", stateKey, location); 24 | var subjectB = new CacheKey("OK", stateKey, location); 25 | 26 | Assert.That(subjectA.Equals(subjectB), Is.True); 27 | } 28 | 29 | [Test] 30 | public void Equals_WithNullReference_ReturnsFalse() 31 | { 32 | var subjectA = new CacheKey("OK", 0, 0); 33 | var subjectB = (CacheKey)null; 34 | 35 | Assert.That(subjectA.Equals(subjectB), Is.False); 36 | } 37 | 38 | [Test] 39 | public void GetHashCode_WithIdenticalCacheKeys_ReturnsTheSameValue([Values(0, 1, 2)] int stateKey, [Values(0, 1, 2)] int location) 40 | { 41 | var subjectA = new CacheKey("OK", stateKey, location); 42 | var subjectB = new CacheKey("OK", stateKey, location); 43 | 44 | Assert.That(subjectA.GetHashCode(), Is.EqualTo(subjectB.GetHashCode())); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Pegasus.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportMissingRulesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | using Pegasus.Properties; 9 | 10 | internal class ReportMissingRulesPass : CompilePass 11 | { 12 | public override IList BlockedByErrors => new[] { "PEG0001" }; 13 | 14 | public override IList ErrorsProduced => new[] { "PEG0003" }; 15 | 16 | public override void Run(Grammar grammar, CompileResult result) 17 | { 18 | new MissingRuleExpressionTreeWalker(grammar, result).WalkGrammar(grammar); 19 | } 20 | 21 | private class MissingRuleExpressionTreeWalker : ExpressionTreeWalker 22 | { 23 | private readonly HashSet knownRules; 24 | private readonly CompileResult result; 25 | 26 | public MissingRuleExpressionTreeWalker(Grammar grammar, CompileResult result) 27 | { 28 | this.knownRules = new HashSet(grammar.Rules.Select(r => r.Identifier.Name)); 29 | this.result = result; 30 | } 31 | 32 | protected override void WalkNameExpression(NameExpression nameExpression) 33 | { 34 | var name = nameExpression.Identifier.Name; 35 | if (!this.knownRules.Contains(name)) 36 | { 37 | var cursor = nameExpression.Identifier.Start; 38 | this.result.AddCompilerError(cursor, () => Resources.PEG0003_ERROR_RuleDoesNotExist, name); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Pegasus.Package/PegasusPackage.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Package 4 | { 5 | using System; 6 | using System.ComponentModel.Design; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Runtime.InteropServices; 9 | using Microsoft.VisualStudio.Shell; 10 | 11 | /// 12 | /// Implements the package exposed by this assembly. 13 | /// 14 | [PackageRegistration(UseManagedResourcesOnly = true)] 15 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 16 | [Guid(GuidList.PegasusPackageGuid)] 17 | [ProvideBindingPath] 18 | [ProvideService(typeof(PegasusLanguageService), ServiceName = "Pegasus Language Service")] 19 | [ProvideLanguageService(typeof(PegasusLanguageService), "Pegasus", 110, CodeSense = true, RequestStockColors = true, EnableCommenting = true, EnableAsyncCompletion = true)] 20 | [ProvideLanguageExtension(typeof(PegasusLanguageService), ".peg")] 21 | public sealed class PegasusPackage : Package 22 | { 23 | /// 24 | /// Called when the VSPackage is loaded by Visual Studio. 25 | /// 26 | [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The language service is added to the service container, which manages its lifetime.")] 27 | protected override void Initialize() 28 | { 29 | base.Initialize(); 30 | 31 | var serviceContainer = this as IServiceContainer; 32 | var langService = new PegasusLanguageService(); 33 | langService.SetSite(this); 34 | serviceContainer.AddService(typeof(PegasusLanguageService), langService, true); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pegasus/Compiler/GenerateCodePass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Globalization; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Reflection; 11 | using Pegasus.Expressions; 12 | using Pegasus.Properties; 13 | 14 | internal class GenerateCodePass : CompilePass 15 | { 16 | private static readonly Lazy> AllErrors = new Lazy>(() => 17 | { 18 | var additionalErrors = new[] { "CS0000" }; 19 | 20 | return (from p in typeof(Resources).GetProperties(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty) 21 | let parts = p.Name.Split('_') 22 | where parts.Length == 3 23 | where parts[1] == "ERROR" 24 | select parts[0]) 25 | .Concat(additionalErrors) 26 | .ToList() 27 | .AsReadOnly(); 28 | }); 29 | 30 | public override IList BlockedByErrors => AllErrors.Value; 31 | 32 | public override IList ErrorsProduced => new string[0]; 33 | 34 | public override void Run(Grammar grammar, CompileResult result) 35 | { 36 | if (result.Errors.Any(e => !e.IsWarning)) 37 | { 38 | return; 39 | } 40 | 41 | using (var stringWriter = new StringWriter(CultureInfo.InvariantCulture)) 42 | { 43 | new CodeGenerator(stringWriter, result.ExpressionTypes, result.LeftRecursiveRules).WalkGrammar(grammar); 44 | result.Code = stringWriter.ToString(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Package/Package.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {7D630AC1-62A7-439F-AC80-0EA800F3A846} 7 | v4.8 8 | Library 9 | 10 | 11 | bin\Debug\ 12 | 13 | 14 | bin\Release\ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Pegasus.Tests/CultureUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests 4 | { 5 | using System; 6 | using System.Globalization; 7 | using System.Threading; 8 | 9 | internal static class CultureUtilities 10 | { 11 | public static void WithCulture(string culture, Action action) 12 | { 13 | var cultureInfo = CultureInfo.GetCultureInfo(culture); 14 | var currentThread = Thread.CurrentThread; 15 | var originalCulture = currentThread.CurrentCulture; 16 | var originalUICulture = currentThread.CurrentUICulture; 17 | try 18 | { 19 | currentThread.CurrentCulture = cultureInfo; 20 | currentThread.CurrentUICulture = cultureInfo; 21 | action(); 22 | } 23 | finally 24 | { 25 | currentThread.CurrentUICulture = originalUICulture; 26 | currentThread.CurrentCulture = originalCulture; 27 | } 28 | } 29 | 30 | public static T WithCulture(string culture, Func action) 31 | { 32 | var cultureInfo = CultureInfo.GetCultureInfo(culture); 33 | var currentThread = Thread.CurrentThread; 34 | var originalCulture = currentThread.CurrentCulture; 35 | var originalUICulture = currentThread.CurrentUICulture; 36 | try 37 | { 38 | currentThread.CurrentCulture = cultureInfo; 39 | currentThread.CurrentUICulture = cultureInfo; 40 | return action(); 41 | } 42 | finally 43 | { 44 | currentThread.CurrentUICulture = originalUICulture; 45 | currentThread.CurrentCulture = originalCulture; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Pegasus/Expressions/ClassExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | /// 10 | /// Represents a of a single character within certain character ranges. 11 | /// 12 | public class ClassExpression : Expression 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The ranges that match. 18 | /// A value indicating whether or not the expression is negated. 19 | /// A value indicating whether or not the expression should ignore case differences when matching. 20 | public ClassExpression(IEnumerable ranges, bool negated = false, bool? ignoreCase = null) 21 | { 22 | if (ranges == null) 23 | { 24 | throw new ArgumentNullException(nameof(ranges)); 25 | } 26 | 27 | this.Ranges = ranges.ToList().AsReadOnly(); 28 | this.Negated = negated; 29 | this.IgnoreCase = ignoreCase; 30 | } 31 | 32 | /// 33 | /// Gets a value indicating whether the expression should ignore case differences when matching. 34 | /// 35 | public bool? IgnoreCase { get; } 36 | 37 | /// 38 | /// Gets a value indicating whether this expression is negated. 39 | /// 40 | public bool Negated { get; } 41 | 42 | /// 43 | /// Gets the character ranges that match. 44 | /// 45 | public IList Ranges { get; } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportInvalidQuantifiersPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | using Pegasus.Properties; 8 | 9 | internal class ReportInvalidQuantifiersPass : CompilePass 10 | { 11 | public override IList BlockedByErrors => new[] { "PEG0001" }; 12 | 13 | public override IList ErrorsProduced => new[] { "PEG0015", "PEG0024" }; 14 | 15 | public override void Run(Grammar grammar, CompileResult result) => new InvalidQuantifierTreeWalker(result).WalkGrammar(grammar); 16 | 17 | private class InvalidQuantifierTreeWalker : ExpressionTreeWalker 18 | { 19 | private readonly CompileResult result; 20 | 21 | public InvalidQuantifierTreeWalker(CompileResult result) 22 | { 23 | this.result = result; 24 | } 25 | 26 | protected override void WalkRepetitionExpression(RepetitionExpression repetitionExpression) 27 | { 28 | if (repetitionExpression.Quantifier.Max == 0 || 29 | repetitionExpression.Quantifier.Max < repetitionExpression.Quantifier.Min) 30 | { 31 | this.result.AddCompilerError(repetitionExpression.Quantifier.Start, () => Resources.PEG0015_WARNING_QuantifierInvalid); 32 | } 33 | 34 | if (repetitionExpression.Quantifier.Max == 1 && 35 | repetitionExpression.Quantifier.Delimiter != null) 36 | { 37 | this.result.AddCompilerError(repetitionExpression.Quantifier.Start, () => Resources.PEG0024_WARNING_UnusedDelimiter); 38 | } 39 | 40 | base.WalkRepetitionExpression(repetitionExpression); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Pegasus/Expressions/Identifier.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using System.Diagnostics; 7 | using Pegasus.Common; 8 | 9 | /// 10 | /// Represents a lexical identifier. 11 | /// 12 | [DebuggerDisplay("{Name}")] 13 | public class Identifier 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The name of the . 19 | /// The cursor just before the . 20 | /// The cursor just after the . 21 | public Identifier(string name, Cursor start, Cursor end) 22 | { 23 | if (string.IsNullOrEmpty(name)) 24 | { 25 | throw new ArgumentNullException(nameof(name)); 26 | } 27 | 28 | this.Name = name; 29 | this.Start = start ?? throw new ArgumentNullException(nameof(start)); 30 | this.End = end ?? throw new ArgumentNullException(nameof(end)); 31 | } 32 | 33 | /// 34 | /// Gets the cursor just after the . 35 | /// 36 | public Cursor End { get; } 37 | 38 | /// 39 | /// Gets the name of the . 40 | /// 41 | public string Name { get; } 42 | 43 | /// 44 | /// Gets the cursor just before the . 45 | /// 46 | public Cursor Start { get; } 47 | 48 | /// 49 | public override string ToString() 50 | { 51 | return this.Name; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pegasus.Common/ListNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | using System.Collections.Generic; 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | /// 9 | /// Provides static methods for operating on objects. 10 | /// 11 | public static class ListNode 12 | { 13 | /// 14 | /// Prepends a element to the given read-only . 15 | /// 16 | /// The type of the elements in the list. 17 | /// The list being prepended. 18 | /// The value to prepend to the list. 19 | /// A new read-only list with the given value prepended. 20 | public static ListNode Push(this ListNode @this, T value) => new ListNode(value, @this); 21 | 22 | /// 23 | /// Converts a read-only into a . 24 | /// 25 | /// The type of the elements in the list. 26 | /// The list to convert. 27 | /// A containing the elements in the read-only . 28 | [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "This does not expose an internal list, it is a utility method explicitly for the purpose of creating generic lists.")] 29 | public static List ToList(this ListNode @this) 30 | { 31 | var list = new List(); 32 | 33 | while (@this != null) 34 | { 35 | list.Add(@this.Head); 36 | @this = @this.Tail; 37 | } 38 | 39 | return list; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Pegasus/Expressions/Grammar.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Pegasus.Common; 9 | 10 | /// 11 | /// Represents a full set of grammar rules. 12 | /// 13 | public class Grammar 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The rules for this . 19 | /// A collection of to be used as the settings for the compiler. 20 | /// The ending cursor for this . 21 | public Grammar(IEnumerable rules, IEnumerable> settings, Cursor end) 22 | { 23 | if (rules == null) 24 | { 25 | throw new ArgumentNullException(nameof(rules)); 26 | } 27 | 28 | this.Rules = rules.ToList().AsReadOnly(); 29 | this.Settings = (settings ?? Enumerable.Empty>()).ToList().AsReadOnly(); 30 | this.End = end ?? throw new ArgumentNullException(nameof(end)); 31 | } 32 | 33 | /// 34 | /// Gets the ending cursor for this . 35 | /// 36 | public Cursor End { get; } 37 | 38 | /// 39 | /// Gets the rules for this . 40 | /// 41 | public IList Rules { get; } 42 | 43 | /// 44 | /// Gets the settings for this . 45 | /// 46 | public IList> Settings { get; } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Pegasus/Expressions/CodeSpan.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using Pegasus.Common; 7 | 8 | /// 9 | /// Tracks the contents and region of a code expression. 10 | /// 11 | public class CodeSpan 12 | { 13 | private readonly string value; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The string contents of the code span. 19 | /// The start of the code region. 20 | /// The end of the code region. 21 | /// The value of the code span. 22 | public CodeSpan(string code, Cursor start, Cursor end, string value = null) 23 | { 24 | this.Code = code ?? throw new ArgumentNullException(nameof(code)); 25 | this.Start = start ?? throw new ArgumentNullException(nameof(start)); 26 | this.End = end ?? throw new ArgumentNullException(nameof(end)); 27 | this.value = value ?? code; 28 | } 29 | 30 | /// 31 | /// Gets the contents of the code span. 32 | /// 33 | public string Code { get; } 34 | 35 | /// 36 | /// Gets the start of the code region. 37 | /// 38 | public Cursor End { get; } 39 | 40 | /// 41 | /// Gets the end of the code region. 42 | /// 43 | public Cursor Start { get; } 44 | 45 | /// 46 | /// Returns the string value of this . 47 | /// 48 | /// The string value of this . 49 | public override string ToString() => this.value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Pegasus.Package/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pegasus 6 | Provides Language Services for Pegasus Grammars 7 | http://otac0n.com/Pegasus/ 8 | https://github.com/otac0n/Pegasus/wiki/Getting-Started 9 | Resources\Package.ico 10 | Resources\Pegasus.png 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Pegasus/Compiler/VisibleRules.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | 9 | /// 10 | /// Describles the publicly visible rules for a grammar. 11 | /// 12 | public class VisibleRules 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The starting rule. This will be exposed via the Parse method. 18 | /// The public rules. These will be exposed via ParseRuleName methods. 19 | /// The exported rules. These will be exposed via the Exported collection. 20 | public VisibleRules(Rule startRule, List publicRules, List exportedRules) 21 | { 22 | this.StartRule = startRule; 23 | this.PublicRules = publicRules.ToList().AsReadOnly(); 24 | this.ExportedRules = exportedRules.ToList().AsReadOnly(); 25 | } 26 | 27 | /// 28 | /// Gets the exported rules. 29 | /// 30 | /// 31 | /// These will be exposed via the Exported collection. 32 | /// 33 | public IList ExportedRules { get; } 34 | 35 | /// 36 | /// Gets the public rules. 37 | /// 38 | /// 39 | /// These will be exposed via ParseRuleName methods. 40 | /// 41 | public IList PublicRules { get; } 42 | 43 | /// 44 | /// Gets the starting rule. 45 | /// 46 | /// 47 | /// This will be exposed via the Parse method. 48 | /// 49 | public Rule StartRule { get; } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Pegasus/Expressions/CodeExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Describes the semantics of a code expression. 9 | /// 10 | public enum CodeType 11 | { 12 | /// 13 | /// Indicates that the code should be used as a successful result. 14 | /// 15 | Result, 16 | 17 | /// 18 | /// Indicates that the code should be used to throw an error. 19 | /// 20 | Error, 21 | 22 | /// 23 | /// Indicates that the code should be used to modify the current parser state. 24 | /// 25 | State, 26 | 27 | /// 28 | /// Indicates that the code should be used as the body of a parse method. 29 | /// 30 | Parse, 31 | } 32 | 33 | /// 34 | /// Represents a code expression to be emitted in the source code of the generated parser. 35 | /// 36 | public class CodeExpression : Expression 37 | { 38 | /// 39 | /// Initializes a new instance of the class. 40 | /// 41 | /// The literal code to be contained by this expression. 42 | /// The semantic usage of this expression. 43 | public CodeExpression(CodeSpan codeSpan, CodeType codeType) 44 | { 45 | this.CodeSpan = codeSpan ?? throw new ArgumentNullException(nameof(codeSpan)); 46 | this.CodeType = codeType; 47 | } 48 | 49 | /// 50 | /// Gets the code that this expression contains. 51 | /// 52 | public CodeSpan CodeSpan { get; } 53 | 54 | /// 55 | /// Gets the semantic usage of this expression. 56 | /// 57 | public CodeType CodeType { get; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/CodeSpanTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using System; 6 | using NUnit.Framework; 7 | using Pegasus.Common; 8 | using Pegasus.Expressions; 9 | 10 | [TestFixture] 11 | public class CodeSpanTests 12 | { 13 | [Test] 14 | public void Constructor_WhenGivenNullCode_ThrowsException() 15 | { 16 | var start = new Cursor("OK"); 17 | var end = start.Advance(2); 18 | 19 | Assert.That(() => new CodeSpan(null, start, end), Throws.InstanceOf()); 20 | } 21 | 22 | [Test] 23 | public void Constructor_WhenGivenNullEndCursor_ThrowsException() 24 | { 25 | var start = new Cursor("OK"); 26 | var end = start.Advance(2); 27 | 28 | Assert.That(() => new CodeSpan("OK", start, null), Throws.InstanceOf()); 29 | } 30 | 31 | [Test] 32 | public void Constructor_WhenGivenNullStartCursor_ThrowsException() 33 | { 34 | var start = new Cursor("OK"); 35 | var end = start.Advance(2); 36 | 37 | Assert.That(() => new CodeSpan("OK", null, end), Throws.InstanceOf()); 38 | } 39 | 40 | [Test] 41 | public void ToString_WhenConstructedWithANonNullValue_ReturnsTheValue() 42 | { 43 | var start = new Cursor("OK"); 44 | var end = start.Advance(2); 45 | var codeSpan = new CodeSpan("OK", start, end, "value"); 46 | 47 | Assert.That(codeSpan.ToString(), Is.EqualTo("value")); 48 | } 49 | 50 | [Test] 51 | public void ToString_WhenConstructedWithANullValue_ReturnsTheCodeVerbatim() 52 | { 53 | var start = new Cursor("OK"); 54 | var end = start.Advance(2); 55 | var codeSpan = new CodeSpan("OK", start, end); 56 | 57 | Assert.That(codeSpan.ToString(), Is.EqualTo("OK")); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Pegasus/Expressions/SequenceExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | /// 10 | /// Represents a sequence of expressions to match. 11 | /// 12 | public class SequenceExpression : Expression 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The sequence of expressions to match. 18 | public SequenceExpression(IEnumerable sequence) 19 | { 20 | if (sequence == null) 21 | { 22 | throw new ArgumentNullException(nameof(sequence)); 23 | } 24 | 25 | var flattened = new List(); 26 | foreach (var e in sequence) 27 | { 28 | LiteralExpression literalExpression; 29 | if (e is SequenceExpression sequenceExpression) 30 | { 31 | var last = sequenceExpression.Sequence.LastOrDefault() as CodeExpression; 32 | if (last == null || last.CodeType == CodeType.State || last.CodeType == CodeType.Error) 33 | { 34 | flattened.AddRange(sequenceExpression.Sequence); 35 | continue; 36 | } 37 | } 38 | else if ((literalExpression = e as LiteralExpression) != null) 39 | { 40 | if (string.IsNullOrEmpty(literalExpression.Value)) 41 | { 42 | continue; 43 | } 44 | } 45 | 46 | flattened.Add(e); 47 | } 48 | 49 | this.Sequence = flattened.AsReadOnly(); 50 | } 51 | 52 | /// 53 | /// Gets the sequence of expressions to match. 54 | /// 55 | public IList Sequence { get; } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Pegasus.Workbench/Pipeline/PegCompiler.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench.Pipeline 4 | { 5 | using System; 6 | using System.CodeDom.Compiler; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Reactive.Concurrency; 10 | using System.Reactive.Linq; 11 | using Pegasus.Compiler; 12 | using Pegasus.Expressions; 13 | 14 | internal sealed class PegCompiler 15 | { 16 | public PegCompiler(IObservable grammars) 17 | { 18 | var compileResults = grammars 19 | .ObserveOn(Scheduler.Default) 20 | .Select(grammar => 21 | { 22 | if (grammar == null) 23 | { 24 | return grammar; 25 | } 26 | 27 | var trace = grammar.Settings.Where(s => s.Key.Name == SettingName.Trace).SingleOrDefault(); 28 | var identifier = trace.Key; 29 | if (identifier != null && trace.Value.ToString() == "true") 30 | { 31 | return grammar; 32 | } 33 | 34 | identifier = identifier ?? new Identifier(SettingName.Trace, grammar.End, grammar.End); 35 | var traceSetting = new KeyValuePair(identifier, "true"); 36 | return new Grammar( 37 | grammar.Rules, 38 | grammar.Settings.Where(s => s.Key.Name != SettingName.Trace).Concat(new[] { traceSetting }), 39 | grammar.End); 40 | }) 41 | .Select(r => r == null ? new Compiler.CompileResult(r) : Compiler.PegCompiler.Compile(r)) 42 | .Publish() 43 | .RefCount(); 44 | 45 | this.Codes = compileResults.Select(r => r.Code); 46 | this.Errors = compileResults.Select(r => r.Errors.ToList().AsReadOnly()); 47 | } 48 | 49 | public IObservable Codes { get; } 50 | 51 | public IObservable> Errors { get; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Pegasus/Compiler/PegCompiler.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using Pegasus.Expressions; 10 | 11 | /// 12 | /// Provides error checking and compilation services for PEG grammars. 13 | /// 14 | public static class PegCompiler 15 | { 16 | private static readonly IList PassTypes = Assembly.GetExecutingAssembly().GetTypes() 17 | .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(CompilePass))) 18 | .ToList() 19 | .AsReadOnly(); 20 | 21 | /// 22 | /// Compiles a PEG grammar into a program. 23 | /// 24 | /// The grammar to compile. 25 | /// A containing the errors, warnings, and results of compilation. 26 | public static CompileResult Compile(Grammar grammar) 27 | { 28 | var result = new CompileResult(grammar); 29 | 30 | var passes = PassTypes.Select(t => (CompilePass)Activator.CreateInstance(t)).ToList(); 31 | while (true) 32 | { 33 | var existingErrors = new HashSet(result.Errors.Where(e => !e.IsWarning).Select(e => e.ErrorNumber)); 34 | var pendingErrors = new HashSet(passes.SelectMany(p => p.ErrorsProduced)); 35 | 36 | var nextPasses = passes 37 | .Where(p => !p.BlockedByErrors.Any(e => existingErrors.Contains(e))) 38 | .Where(p => !p.BlockedByErrors.Any(e => pendingErrors.Contains(e))) 39 | .ToList(); 40 | 41 | if (nextPasses.Count == 0) 42 | { 43 | break; 44 | } 45 | 46 | foreach (var pass in nextPasses) 47 | { 48 | pass.Run(grammar, result); 49 | passes.Remove(pass); 50 | } 51 | } 52 | 53 | return result; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Pegasus/Expressions/Rule.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | 10 | /// 11 | /// Represents a parse rule. 12 | /// 13 | [DebuggerDisplay("Rule {Identifier.Name}")] 14 | public class Rule 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The identifier that represents the . 20 | /// The expression that this represents. 21 | /// The flags to be set on this . 22 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "flags", Justification = "The usage of 'flags' here is intentional and desired.")] 23 | public Rule(Identifier identifier, Expression expression, IEnumerable flags) 24 | { 25 | this.Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier)); 26 | this.Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 27 | this.Flags = (flags ?? Enumerable.Empty()).ToList().AsReadOnly(); 28 | } 29 | 30 | /// 31 | /// Gets the expression that this represents. 32 | /// 33 | public Expression Expression { get; } 34 | 35 | /// 36 | /// Gets the flags that have been set on this . 37 | /// 38 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Justification = "The usage of 'Flags' here is intentional and desired.")] 39 | public IList Flags { get; } 40 | 41 | /// 42 | /// Gets the name of this . 43 | /// 44 | public Identifier Identifier { get; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Pegasus/Parser/CSharpParser.peg: -------------------------------------------------------------------------------- 1 | @namespace Pegasus.Parser 2 | @classname CSharpParser 3 | @accessibility internal 4 | @using Microsoft.CodeAnalysis.CSharp 5 | @using Microsoft.CodeAnalysis.CSharp.Syntax 6 | @members 7 | { 8 | private static ParseResult ParseHelper(ref Cursor cursor, T syntax) 9 | where T : CSharpSyntaxNode 10 | { 11 | if (syntax.Span.IsEmpty) 12 | { 13 | return null; 14 | } 15 | 16 | var start = cursor; 17 | cursor = start.Advance(syntax.FullSpan.End); 18 | return new ParseResult(start, cursor, syntax); 19 | } 20 | } 21 | 22 | ArgumentList -export = #parse{ 23 | ParseHelper(ref state, SyntaxFactory.ParseArgumentList(state.Subject, state.Location, consumeFullText: false)) 24 | } 25 | 26 | AttributeArgumentList -export = #parse{ 27 | ParseHelper(ref state, SyntaxFactory.ParseAttributeArgumentList(state.Subject, state.Location, consumeFullText: false)) 28 | } 29 | 30 | BracketedArgumentList -export = #parse{ 31 | ParseHelper(ref state, SyntaxFactory.ParseBracketedArgumentList(state.Subject, state.Location, consumeFullText: false)) 32 | } 33 | 34 | BracketedParameterList -export = #parse{ 35 | ParseHelper(ref state, SyntaxFactory.ParseBracketedParameterList(state.Subject, state.Location, consumeFullText: false)) 36 | } 37 | 38 | Expression -export = #parse{ 39 | ParseHelper(ref state, SyntaxFactory.ParseExpression(state.Subject, state.Location, consumeFullText: false)) 40 | } 41 | 42 | Name -export = #parse{ 43 | ParseHelper(ref state, SyntaxFactory.ParseName(state.Subject, state.Location, consumeFullText: false)) 44 | } 45 | 46 | ParameterList -export = #parse{ 47 | ParseHelper(ref state, SyntaxFactory.ParseParameterList(state.Subject, state.Location, consumeFullText: false)) 48 | } 49 | 50 | Statement -export = #parse{ 51 | ParseHelper(ref state, SyntaxFactory.ParseStatement(state.Subject, state.Location, consumeFullText: false)) 52 | } 53 | 54 | Type -export = #parse{ 55 | ParseHelper(ref state, SyntaxFactory.ParseTypeName(state.Subject, state.Location, consumeFullText: false)) 56 | } 57 | -------------------------------------------------------------------------------- /Package/Pegasus.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(MSBuildThisFileDirectory)netcoreapp2.0\Pegasus.dll 8 | 9 | 10 | $(MSBuildThisFileDirectory)net45\Pegasus.exe 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | $(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace(%(PegGrammar.Link), '(?<=^|\\|/)..(?=$|\\|/)', '__')).g.cs 24 | 25 | 26 | $(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace(%(PegGrammar.Identity), '(?<=^|\\|/)..(?=$|\\|/)', '__')).g.cs 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Pegasus.Package/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Pegasus.Tests/CompileManagerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests 4 | { 5 | using System; 6 | using System.Linq; 7 | using NUnit.Framework; 8 | 9 | [TestFixture] 10 | public class CompileManagerTests 11 | { 12 | [TestCase(null)] 13 | [TestCase("OK.peg.g.cs")] 14 | public void CompileFile_WhenGivenANullInputFileName_ThrowsArgumentNullException(string outputFile) 15 | { 16 | Assert.That(() => CompileManager.CompileFile(null, outputFile, err => { }), Throws.InstanceOf()); 17 | } 18 | 19 | [TestCase(null)] 20 | [TestCase("OK.peg.g.cs")] 21 | public void CompileFile_WhenGivenANullLogErrorAction_ThrowsArgumentNullException(string outputFile) 22 | { 23 | Assert.That(() => CompileManager.CompileFile("OK.peg", outputFile, null), Throws.InstanceOf()); 24 | } 25 | 26 | [TestCase(null, null)] 27 | [TestCase("", null)] 28 | [TestCase(null, "OK.peg")] 29 | [TestCase("", "OK.peg")] 30 | public void CompileString_WhenGivenANullOrEmptyString_ReturnsCompileResultWithErrors(string subject, string fileName) 31 | { 32 | var result = CompileManager.CompileString(subject, fileName); 33 | Assert.That(result.Errors.Single().ErrorNumber, Is.EqualTo("PEG0001")); 34 | } 35 | 36 | [TestCase("start = 'OK'", null)] 37 | [TestCase("start = 'OK'", "OK.peg")] 38 | public void CompileString_WhenGivenAValidGrammar_ReturnsCompileResultWithNoErrors(string subject, string fileName) 39 | { 40 | var result = CompileManager.CompileString(subject, fileName); 41 | Assert.That(result.Errors, Is.Empty); 42 | } 43 | 44 | [TestCase(@"C:\Project\Foo.peg", @"C:\Project\Foo.peg.g.cs", "Foo.peg")] 45 | [TestCase(@"C:\Project\Foo.peg", @"C:\Project\obj\Debug\Foo.peg.g.cs", @"C:\Project\Foo.peg")] 46 | public void MakePragmaPath_WhenGivenVariousOutputPaths_ReturnsTheExpectedPragmaPath(string input, string output, string pragmaPath) 47 | { 48 | var result = CompileManager.MakePragmaPath(input, output); 49 | Assert.That(result, Is.EqualTo(pragmaPath)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportCodeSyntaxIssuesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.CodeDom.Compiler; 6 | using System.Collections.Generic; 7 | using System.Diagnostics.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Pegasus.Expressions; 10 | 11 | internal class ReportCodeSyntaxIssuesPass : CompilePass 12 | { 13 | public override IList BlockedByErrors => new string[0]; 14 | 15 | public override IList ErrorsProduced => new[] { "CS0000" }; 16 | 17 | public override void Run(Grammar grammar, CompileResult result) => new CodeSyntaxTreeWalker(result).WalkGrammar(grammar); 18 | 19 | private class CodeSyntaxTreeWalker : ExpressionTreeWalker 20 | { 21 | private readonly CompileResult result; 22 | 23 | public CodeSyntaxTreeWalker(CompileResult result) 24 | { 25 | this.result = result; 26 | } 27 | 28 | [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseExpression(System.String,System.Int32,Microsoft.CodeAnalysis.ParseOptions,System.Boolean)", Justification = "Not localizable.")] 29 | protected override void WalkCodeExpression(CodeExpression codeExpression) 30 | { 31 | const string prefix = "state =>"; 32 | 33 | var startCursor = codeExpression.CodeSpan.Start; 34 | var code = (prefix + codeExpression.CodeSpan.Code).TrimEnd(); 35 | var expression = SyntaxFactory.ParseExpression(code); 36 | 37 | if (expression.ContainsDiagnostics) 38 | { 39 | foreach (var diag in expression.GetDiagnostics()) 40 | { 41 | var cursor = diag.Location.IsInSource 42 | ? startCursor.Advance(-prefix.Length + diag.Location.SourceSpan.Start) 43 | : startCursor; 44 | 45 | this.result.Errors.Add(new CompilerError(startCursor.FileName ?? string.Empty, cursor.Line, cursor.Column, diag.Id, diag.GetMessage()) { IsWarning = diag.WarningLevel > 0 }); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Pegasus.Common/Pegasus.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | false 5 | false 6 | net35;net40;net45;netstandard1.0;netstandard2.0 7 | true 8 | ..\Key.snk 9 | bin\$(Configuration)\$(TargetFramework)\Pegasus.Common.xml 10 | TRACE 11 | 12 | 13 | $(DefineConstants);DEBUG 14 | 15 | 16 | $(DefineConstants);NETFULL;NET35 17 | 18 | 19 | $(DefineConstants);NETFULL;NET40 20 | 21 | 22 | $(DefineConstants);NETFULL;NET45 23 | 24 | 25 | $(DefineConstants);NETCORE;NETSTANDARD;NETSTANDARD1_0 26 | 27 | 28 | $(DefineConstants);NETCORE;NETSTANDARD;NETSTANDARD2_0 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Pegasus/CompilePegGrammar.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus 4 | { 5 | using System.CodeDom.Compiler; 6 | using System.Linq; 7 | using Microsoft.Build.Framework; 8 | using Microsoft.Build.Utilities; 9 | using Properties; 10 | 11 | /// 12 | /// Provides compilation services for PEG grammars as an MSBuild task. 13 | /// 14 | public class CompilePegGrammar : Task 15 | { 16 | /// 17 | /// Gets or sets the filenames containing grammars in PEG-format. 18 | /// 19 | [Required] 20 | public string[] InputFiles { get; set; } 21 | 22 | /// 23 | /// Gets or sets the output filenames that will contain the resulting code. 24 | /// 25 | /// 26 | /// Set to null to use the default, which is the input filenames with ".g.cs" appended. 27 | /// 28 | public string[] OutputFiles { get; set; } 29 | 30 | /// 31 | /// Reads and compiles the specified grammars. 32 | /// 33 | /// true, if the compilation was successful; false, otherwise. 34 | public override bool Execute() 35 | { 36 | var inputs = this.InputFiles.ToList(); 37 | var outputs = (this.OutputFiles ?? new string[inputs.Count]).ToList(); 38 | 39 | if (inputs.Count != outputs.Count) 40 | { 41 | this.Log.LogError(Resources.PEG0027_ERROR_WrongNumberOfOutputFiles); 42 | return false; 43 | } 44 | 45 | for (var i = 0; i < this.InputFiles.Length; i++) 46 | { 47 | CompileManager.CompileFile(inputs[i], outputs[i], this.LogError); 48 | } 49 | 50 | return !this.Log.HasLoggedErrors; 51 | } 52 | 53 | private void LogError(CompilerError error) 54 | { 55 | if (error.IsWarning) 56 | { 57 | this.Log.LogWarning(null, error.ErrorNumber, null, error.FileName, error.Line, error.Column, 0, 0, error.ErrorText); 58 | } 59 | else 60 | { 61 | this.Log.LogError(null, error.ErrorNumber, null, error.FileName, error.Line, error.Column, 0, 0, error.ErrorText); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/Sequence.weave: -------------------------------------------------------------------------------- 1 | {{ 2 | var finalContext = (ResultContext)model.FinalContext; 3 | var index = (int)model.Index; 4 | var sequence = (IList)model.Sequence; 5 | var startCursorName = (string)model.StartCursorName; 6 | 7 | var expression = sequence[index]; 8 | 9 | var codeExpression = expression as CodeExpression; 10 | if (codeExpression != null && codeExpression.CodeType != CodeType.Error && codeExpression.CodeType != CodeType.Result) 11 | { 12 | codeExpression = null; 13 | } 14 | }} 15 | {{if expression == null}} 16 | { 17 | var len = cursor.Location - {{: startCursorName }}.Location; 18 | {{: finalContext.ResultName }} = this.ReturnHelper<{{= finalContext.ResultType }}>({{: startCursorName }}, ref cursor, state => 19 | state.Subject.Substring({{: startCursorName }}.Location, len) 20 | {{if finalContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(finalContext.ResultRuleName) }}{{/if}}); 21 | } 22 | {{elif codeExpression != null}} 23 | {{if codeExpression.CodeType == CodeType.Result}} 24 | {{: finalContext.ResultName }} = this.ReturnHelper<{{= finalContext.ResultType }}>({{: startCursorName }}, ref cursor, state => 25 | {{@RenderCode codeExpression.CodeSpan}} 26 | {{if finalContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(finalContext.ResultRuleName) }}{{/if}}); 27 | {{elif codeExpression.CodeType == CodeType.Error}} 28 | {{@WalkExpression expression}} 29 | {{else}} 30 | {{ 31 | throw new NotImplementedException(); 32 | }} 33 | {{/if}} 34 | {{else}} 35 | {{ 36 | this.currentContext = new ResultContext( 37 | resultName: this.CreateVariable("r"), 38 | resultType: this.types[expression]); 39 | }} 40 | {{if this.currentContext.ResultType is CodeSpan}} 41 | IParseResult< 42 | {{@RenderCode this.currentContext.ResultType}} 43 | > {{: this.currentContext.ResultName }} = null; 44 | {{else}} 45 | IParseResult<{{= this.currentContext.ResultType }}> {{: this.currentContext.ResultName }} = null; 46 | {{/if}} 47 | {{@WalkExpression expression}} 48 | if ({{: this.currentContext.ResultName }} != null) 49 | { 50 | {{@RenderSequence new { FinalContext = finalContext, Index = index + 1, Sequence = sequence, StartCursorName = startCursorName } }} 51 | } 52 | else 53 | { 54 | cursor = {{: startCursorName }}; 55 | } 56 | {{/if}} -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportConflictingNamesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using Pegasus.Expressions; 7 | using Pegasus.Properties; 8 | 9 | internal class ReportConflictingNamesPass : CompilePass 10 | { 11 | public override IList BlockedByErrors => new[] { "PEG0001" }; 12 | 13 | public override IList ErrorsProduced => new[] { "PEG0007" }; 14 | 15 | public override void Run(Grammar grammar, CompileResult result) => new ConflictingNamesTreeWalker(result).WalkGrammar(grammar); 16 | 17 | private class ConflictingNamesTreeWalker : ExpressionTreeWalker 18 | { 19 | private readonly HashSet currentNames = new HashSet(); 20 | private readonly CompileResult result; 21 | 22 | public ConflictingNamesTreeWalker(CompileResult result) 23 | { 24 | this.result = result; 25 | } 26 | 27 | protected override void WalkChoiceExpression(ChoiceExpression choiceExpression) 28 | { 29 | var namesCopy = new HashSet(this.currentNames); 30 | foreach (var expression in choiceExpression.Choices) 31 | { 32 | this.WalkExpression(expression); 33 | this.currentNames.IntersectWith(namesCopy); 34 | } 35 | } 36 | 37 | protected override void WalkPrefixedExpression(PrefixedExpression prefixedExpression) 38 | { 39 | var success = this.currentNames.Add(prefixedExpression.Prefix.Name); 40 | this.currentNames.Add(prefixedExpression.Prefix.Name + "Start"); 41 | this.currentNames.Add(prefixedExpression.Prefix.Name + "End"); 42 | 43 | if (!success) 44 | { 45 | var cursor = prefixedExpression.Prefix.Start; 46 | this.result.AddCompilerError(cursor, () => Resources.PEG0007_ERROR_PrefixAlreadyDeclared, prefixedExpression.Prefix.Name); 47 | } 48 | 49 | base.WalkPrefixedExpression(prefixedExpression); 50 | } 51 | 52 | protected override void WalkRule(Rule rule) 53 | { 54 | base.WalkRule(rule); 55 | this.currentNames.Clear(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Pegasus/Expressions/Quantifier.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using Pegasus.Common; 7 | 8 | /// 9 | /// Represents the rules for repeating an expression. 10 | /// 11 | public class Quantifier 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The cursor just before the . 17 | /// The cursor just after the . 18 | /// The minimum number of times to match. 19 | /// The maximum number of times to match, if limited; or null, otherwise. 20 | /// The expression to use as a delimiter. 21 | public Quantifier(Cursor start, Cursor end, int min, int? max = null, Expression delimiter = null) 22 | { 23 | this.Start = start ?? throw new ArgumentNullException(nameof(start)); 24 | this.End = end ?? throw new ArgumentNullException(nameof(end)); 25 | this.Min = min; 26 | this.Max = max; 27 | 28 | if (delimiter != null) 29 | { 30 | SequenceExpression sequenceExpression; 31 | if ((sequenceExpression = delimiter as SequenceExpression) == null || sequenceExpression.Sequence.Count != 0) 32 | { 33 | this.Delimiter = delimiter; 34 | } 35 | } 36 | } 37 | 38 | /// 39 | /// Gets the expression to use as a delimiter. 40 | /// 41 | public Expression Delimiter { get; } 42 | 43 | /// 44 | /// Gets the cursor just after the . 45 | /// 46 | public Cursor End { get; } 47 | 48 | /// 49 | /// Gets the maximum number of times to match, if limited; or null, if there is no limit. 50 | /// 51 | public int? Max { get; } 52 | 53 | /// 54 | /// Gets the minimum number of times to match. 55 | /// 56 | public int Min { get; } 57 | 58 | /// 59 | /// Gets the cursor just before the . 60 | /// 61 | public Cursor Start { get; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Pegasus.Workbench/PathToFileNameConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Workbench 4 | { 5 | using System; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Windows.Data; 9 | using System.Windows.Markup; 10 | 11 | /// 12 | /// Converts paths to just their file names. 13 | /// 14 | public class PathToFileNameConverter : MarkupExtension, IValueConverter 15 | { 16 | /// 17 | /// Converts a value. 18 | /// 19 | /// The value produced by the binding source. 20 | /// The type of the binding target property. 21 | /// The converter parameter to use. 22 | /// The culture to use in the converter. 23 | /// A converted value. If the method returns null, the valid null value is used. 24 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 25 | { 26 | if (targetType != typeof(string)) 27 | { 28 | throw new NotSupportedException(); 29 | } 30 | 31 | return value == null ? null : Path.GetFileName(value.ToString()); 32 | } 33 | 34 | /// 35 | /// Converts a value. 36 | /// 37 | /// The value that is produced by the binding target. 38 | /// The type to convert to. 39 | /// The converter parameter to use. 40 | /// The culture to use in the converter. 41 | /// A converted value. If the method returns null, the valid null value is used. 42 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 43 | { 44 | throw new NotSupportedException(); 45 | } 46 | 47 | /// 48 | /// When implemented in a derived class, returns an object that is provided as the value of the target property for this markup extension. 49 | /// 50 | /// A service provider helper that can provide services for the markup extension. 51 | /// The object value to set on the property where the extension is applied. 52 | public override object ProvideValue(IServiceProvider serviceProvider) => this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pegasus/Expressions/LiteralExpression.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | using Pegasus.Common; 7 | 8 | /// 9 | /// Represents a literal string. 10 | /// 11 | public class LiteralExpression : Expression 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The cursor just before the . 17 | /// The cursor just after the . 18 | /// The literal value. 19 | /// A value indicating whether or not the expression should ignore case differences when matching. 20 | /// A value indicating whether corresponds to a resource name or a literal value. 21 | public LiteralExpression(Cursor start, Cursor end, string value, bool? ignoreCase = null, bool fromResource = false) 22 | { 23 | this.Start = start ?? throw new ArgumentNullException(nameof(start)); 24 | this.End = end ?? throw new ArgumentNullException(nameof(end)); 25 | this.Value = value ?? throw new ArgumentNullException(nameof(value)); 26 | this.IgnoreCase = ignoreCase; 27 | this.FromResource = fromResource; 28 | } 29 | 30 | /// 31 | /// Gets the cursor just after the . 32 | /// 33 | public Cursor End { get; } 34 | 35 | /// 36 | /// Gets a value indicating whether corresponds to a resource name or a literal value. 37 | /// 38 | /// 39 | /// True, if corresponds to a resource name that will be used as the literal value; false, otherwise. 40 | /// 41 | public bool FromResource { get; } 42 | 43 | /// 44 | /// Gets a value indicating whether the expression should ignore case differences when matching. 45 | /// 46 | public bool? IgnoreCase { get; } 47 | 48 | /// 49 | /// Gets the cursor just before the . 50 | /// 51 | public Cursor Start { get; } 52 | 53 | /// 54 | /// Gets the value of this expression. 55 | /// 56 | public string Value { get; } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Pegasus.Common/Tracing/DiagnosticsTracer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | #if !NETSTANDARD1_0 4 | 5 | namespace Pegasus.Common.Tracing 6 | { 7 | using System.Diagnostics; 8 | using System.Diagnostics.CodeAnalysis; 9 | 10 | /// 11 | /// Implements an that writes to . 12 | /// 13 | public class DiagnosticsTracer : ITracer 14 | { 15 | private DiagnosticsTracer() 16 | { 17 | } 18 | 19 | /// 20 | /// Gets the instance of . 21 | /// 22 | public static DiagnosticsTracer Instance { get; } = new DiagnosticsTracer(); 23 | 24 | /// 25 | public void TraceCacheHit(string ruleName, Cursor cursor, CacheKey cacheKey, IParseResult parseResult) => this.TraceInfo(ruleName, cursor, "Cache hit."); 26 | 27 | /// 28 | public void TraceCacheMiss(string ruleName, Cursor cursor, CacheKey cacheKey) => this.TraceInfo(ruleName, cursor, "Cache miss."); 29 | 30 | /// 31 | public void TraceInfo(string ruleName, Cursor cursor, string info) => Trace.WriteLine(info); 32 | 33 | /// 34 | [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])", Justification = "This is fine.")] 35 | [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validation excluded for performance reasons.")] 36 | public void TraceRuleEnter(string ruleName, Cursor cursor) 37 | { 38 | Trace.WriteLine($"Begin '{ruleName}' at ({cursor.Line},{cursor.Column}) with state key {cursor.StateKey}"); 39 | Trace.Indent(); 40 | } 41 | 42 | /// 43 | [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])", Justification = "This is fine.")] 44 | [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validation excluded for performance reasons.")] 45 | public void TraceRuleExit(string ruleName, Cursor cursor, IParseResult parseResult) 46 | { 47 | var success = parseResult != null; 48 | Trace.Unindent(); 49 | Trace.WriteLine($"End '{ruleName}' with {(success ? "success" : "failure")} at ({cursor.Line},{cursor.Column}) with state key {cursor.StateKey}"); 50 | } 51 | } 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /Pegasus.Common/CacheKey.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common 4 | { 5 | using System.Diagnostics; 6 | 7 | /// 8 | /// A high-performance cache key for rule memoization. 9 | /// 10 | [DebuggerDisplay("{ruleName}:{location}:{stateKey}")] 11 | public class CacheKey 12 | { 13 | private readonly int hash; 14 | private readonly int location; 15 | private readonly string ruleName; 16 | private readonly int stateKey; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The name of the rule. 22 | /// The state key of the current cursor. 23 | /// The location of the current cursor. 24 | public CacheKey(string ruleName, int stateKey, int location) 25 | { 26 | this.ruleName = ruleName; 27 | this.stateKey = stateKey; 28 | this.location = location; 29 | 30 | unchecked 31 | { 32 | var hash = 0x51ED270B; 33 | hash = (hash * -0x25555529) + (this.ruleName == null ? 0 : this.ruleName.GetHashCode()); 34 | hash = (hash * -0x25555529) + this.stateKey; 35 | hash = (hash * -0x25555529) + this.location; 36 | this.hash = hash; 37 | } 38 | } 39 | 40 | /// 41 | /// Determines whether the specified is equal to the current . 42 | /// 43 | /// The object to compare with the current object. 44 | /// true if the specified object is equal to the current object; otherwise, false. 45 | public override bool Equals(object obj) 46 | { 47 | if (object.ReferenceEquals(this, obj)) 48 | { 49 | return true; 50 | } 51 | 52 | var other = obj as CacheKey; 53 | if (!(other is null)) 54 | { 55 | return 56 | this.location == other.location && 57 | this.stateKey == other.stateKey && 58 | this.ruleName == other.ruleName; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | /// 65 | /// Serves as a hash function for a particular type. 66 | /// 67 | /// A hash code for the current . 68 | public override int GetHashCode() => this.hash; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Pegasus/Compiler/CodeGenerator/RepetitionExpression.weave: -------------------------------------------------------------------------------- 1 | @model RepetitionExpression 2 | {{ 3 | var startCursorName = this.CreateVariable("startCursor"); 4 | var listName = this.CreateVariable("l"); 5 | var oldContext = this.currentContext; 6 | var listResultType = this.types[model.Expression]; 7 | var loopCursorName = model.Quantifier.Delimiter == null ? null : this.CreateVariable("startCursor"); 8 | }} 9 | var {{: startCursorName }} = cursor; 10 | var {{: listName }} = new List<{{= listResultType }}>(); 11 | while ({{if model.Quantifier.Max.HasValue}}{{: listName }}.Count < {{= model.Quantifier.Max }}{{else}}true{{/if}}) 12 | { 13 | {{if model.Quantifier.Delimiter != null}} 14 | {{ 15 | this.currentContext = new ResultContext( 16 | resultName: this.CreateVariable("r"), 17 | resultType: this.types[model.Quantifier.Delimiter]); 18 | }} 19 | var {{: loopCursorName }} = cursor; 20 | if ({{: listName }}.Count > 0) 21 | { 22 | IParseResult<{{= this.currentContext.ResultType }}> {{: this.currentContext.ResultName }} = null; 23 | {{@WalkExpression model.Quantifier.Delimiter}} 24 | if ({{: this.currentContext.ResultName }} == null) 25 | { 26 | break; 27 | } 28 | } 29 | {{/if}} 30 | {{ 31 | this.currentContext = new ResultContext( 32 | resultName: this.CreateVariable("r"), 33 | resultType: listResultType); 34 | }} 35 | IParseResult<{{= this.currentContext.ResultType }}> {{: this.currentContext.ResultName }} = null; 36 | {{@WalkExpression model.Expression}} 37 | if ({{: this.currentContext.ResultName }} != null) 38 | { 39 | {{: listName }}.Add({{: this.currentContext.ResultName }}.Value); 40 | } 41 | else 42 | { 43 | {{if model.Quantifier.Delimiter != null}} 44 | cursor = {{: loopCursorName }}; 45 | {{/if}} 46 | break; 47 | } 48 | } 49 | {{ 50 | this.currentContext = oldContext; 51 | }} 52 | {{if model.Quantifier.Min == 0}} 53 | {{: this.currentContext.ResultName }} = this.ReturnHelper<{{= this.types[model] }}>({{: startCursorName }}, ref cursor, state => {{: listName }}.AsReadOnly(){{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 54 | {{else}} 55 | if ({{: listName }}.Count >= {{= model.Quantifier.Min }}) 56 | { 57 | {{: this.currentContext.ResultName }} = this.ReturnHelper<{{= this.types[model] }}>({{: startCursorName }}, ref cursor, state => {{: listName }}.AsReadOnly(){{if this.currentContext.ResultRuleName != null}}, ruleName: {{= ToLiteral(this.currentContext.ResultRuleName) }}{{/if}}); 58 | } 59 | else 60 | { 61 | cursor = {{: startCursorName }}; 62 | } 63 | {{/if}} 64 | -------------------------------------------------------------------------------- /Pegasus/Compiler/ReportSettingsIssuesPass.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | using Pegasus.Expressions; 8 | using Pegasus.Properties; 9 | 10 | internal class ReportSettingsIssuesPass : CompilePass 11 | { 12 | private static readonly Dictionary KnownSettings = new Dictionary 13 | { 14 | { SettingName.Namespace, true }, 15 | { SettingName.ClassName, true }, 16 | { SettingName.Accessibility, true }, 17 | { SettingName.Resources, true }, 18 | { SettingName.Start, true }, 19 | { SettingName.Members, false }, 20 | { SettingName.Using, false }, 21 | { SettingName.Trace, true }, 22 | { SettingName.IgnoreCase, true }, 23 | }; 24 | 25 | private static readonly Dictionary ValuePatterns = new Dictionary 26 | { 27 | { SettingName.Accessibility, @"^\s*(public|internal)\s*$" }, 28 | { SettingName.Trace, @"^\s*(true|false)\s*$" }, 29 | { SettingName.IgnoreCase, @"^\s*(true|false)\s*$" }, 30 | }; 31 | 32 | public override IList BlockedByErrors => new string[0]; 33 | 34 | public override IList ErrorsProduced => new[] { "PEG0005", "PEG0012" }; 35 | 36 | public override void Run(Grammar grammar, CompileResult result) 37 | { 38 | var seenSettings = new HashSet(); 39 | 40 | foreach (var setting in grammar.Settings) 41 | { 42 | var cursor = setting.Key.Start; 43 | 44 | if (KnownSettings.TryGetValue(setting.Key.Name, out var singleAllowed)) 45 | { 46 | if (singleAllowed && !seenSettings.Add(setting.Key.Name)) 47 | { 48 | result.AddCompilerError(cursor, () => Resources.PEG0005_ERROR_SettingAlreadySpecified, setting.Key.Name); 49 | } 50 | } 51 | else 52 | { 53 | result.AddCompilerError(cursor, () => Resources.PEG0006_WARNING_SettingUnknown, setting.Key.Name); 54 | } 55 | 56 | if (ValuePatterns.TryGetValue(setting.Key.Name, out var pattern)) 57 | { 58 | if (!Regex.IsMatch(setting.Value.ToString(), pattern)) 59 | { 60 | result.AddCompilerError(cursor, () => Resources.PEG0012_ERROR_SettingValueInvalid, setting.Value, setting.Key.Name); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Pegasus/Expressions/CharacterRange.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Expressions 4 | { 5 | using System; 6 | 7 | /// 8 | /// Represents an inclusive range of characters. 9 | /// 10 | public class CharacterRange : IEquatable 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The minimum character value, inclusive. 16 | /// The maximum character value, inclusive. 17 | public CharacterRange(char min, char max) 18 | { 19 | this.Min = min; 20 | this.Max = max; 21 | } 22 | 23 | /// 24 | /// Gets the minimum character value, inclusive. 25 | /// 26 | public char Max { get; } 27 | 28 | /// 29 | /// Gets the maximum character value, inclusive. 30 | /// 31 | public char Min { get; } 32 | 33 | /// 34 | /// Determines whether the specified is equal to the current . 35 | /// 36 | /// The to compare with the current . 37 | /// true if the specified is equal to the current ; otherwise, false. 38 | public override bool Equals(object obj) => this.Equals(obj as CharacterRange); 39 | 40 | /// 41 | /// Determines whether the specified is equal to the current . 42 | /// 43 | /// The to compare with the current . 44 | /// true if the specified is equal to the current ; otherwise, false. 45 | public bool Equals(CharacterRange other) => 46 | other != null && 47 | other.Min == this.Min && 48 | other.Max == this.Max; 49 | 50 | /// 51 | /// Returns the hash code for this instance. 52 | /// 53 | /// A 32-bit signed integer hash code. 54 | public override int GetHashCode() 55 | { 56 | unchecked 57 | { 58 | var hash = 0x51ED270B; 59 | hash = (hash * -0x25555529) + this.Min.GetHashCode(); 60 | hash = (hash * -0x25555529) + this.Max.GetHashCode(); 61 | return hash; 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Pegasus/Compiler/LeftAdjacencyDetector.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Compiler 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Pegasus.Expressions; 8 | 9 | /// 10 | /// Provides left adjacency detection services for Pegasus Grammars. 11 | /// 12 | public static class LeftAdjacencyDetector 13 | { 14 | /// 15 | /// Detects which expressions in a are left-adjacent. 16 | /// 17 | /// The to inspect. 18 | /// A containing the left-adjacent rules. 19 | public static ILookup Detect(Grammar grammar) 20 | { 21 | var leftAdjacent = new Dictionary>(); 22 | var zeroWidth = ZeroWidthEvaluator.Evaluate(grammar); 23 | new LeftRecursionExpressionTreeWalker(zeroWidth, leftAdjacent).WalkGrammar(grammar); 24 | return leftAdjacent.SelectMany(i => i.Value, (i, v) => new { i.Key, Value = v }).ToLookup(i => i.Key, i => i.Value); 25 | } 26 | 27 | private class LeftRecursionExpressionTreeWalker : ExpressionTreeWalker 28 | { 29 | private readonly Dictionary> leftAdjacent; 30 | private Rule currentRule; 31 | private Dictionary zeroWidth; 32 | 33 | public LeftRecursionExpressionTreeWalker(Dictionary zeroWidth, Dictionary> leftAdjacent) 34 | { 35 | this.zeroWidth = zeroWidth; 36 | this.leftAdjacent = leftAdjacent; 37 | } 38 | 39 | public override void WalkExpression(Expression expression) 40 | { 41 | this.leftAdjacent[this.currentRule].Add(expression); 42 | base.WalkExpression(expression); 43 | } 44 | 45 | protected override void WalkRule(Rule rule) 46 | { 47 | this.currentRule = rule; 48 | this.leftAdjacent[this.currentRule] = new List(); 49 | base.WalkRule(rule); 50 | } 51 | 52 | protected override void WalkSequenceExpression(SequenceExpression sequenceExpression) 53 | { 54 | foreach (var expression in sequenceExpression.Sequence) 55 | { 56 | this.WalkExpression(expression); 57 | 58 | if (!this.zeroWidth[expression]) 59 | { 60 | break; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Pegasus.Common/Tracing/ITracer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Common.Tracing 4 | { 5 | /// 6 | /// Provides an interface for PEG grammars to trace their activity. 7 | /// 8 | /// 9 | /// This is primarily intended for debugging. 10 | /// 11 | public interface ITracer 12 | { 13 | /// 14 | /// Signifies that the parser has found a parse result in the cache. 15 | /// 16 | /// The type of the result. 17 | /// The name of the rule being parsed. 18 | /// The cursor where the cache was checked. 19 | /// The cache key used. 20 | /// The result found in the cache. 21 | void TraceCacheHit(string ruleName, Cursor cursor, CacheKey cacheKey, IParseResult parseResult); 22 | 23 | /// 24 | /// Signifies that the parser has not found a parse result in the cache. 25 | /// 26 | /// The name of the rule being parsed. 27 | /// The cursor where the cache was checked. 28 | /// The cache key used. 29 | void TraceCacheMiss(string ruleName, Cursor cursor, CacheKey cacheKey); 30 | 31 | /// 32 | /// Allows the parser to trace additional info relevant to the parse. 33 | /// 34 | /// The name of the rule being parsed. 35 | /// The cursor where the info is presented. 36 | /// The info being included. 37 | void TraceInfo(string ruleName, Cursor cursor, string info); 38 | 39 | /// 40 | /// Signifies that the parser has began parsing the specified rule. 41 | /// 42 | /// The name of the rule being parsed. 43 | /// The cursor where ther parse will be attempted. 44 | void TraceRuleEnter(string ruleName, Cursor cursor); 45 | 46 | /// 47 | /// Signifies that the parser has finished parsing the specified rule. 48 | /// 49 | /// The type of the result. 50 | /// The name of the rule being parsed. 51 | /// The cursor where this rule has left off. 52 | /// The result of the parse. 53 | void TraceRuleExit(string ruleName, Cursor cursor, IParseResult parseResult); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Pegasus.Tests/Expressions/CharacterRangeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests.Expressions 4 | { 5 | using NUnit.Framework; 6 | using Pegasus.Expressions; 7 | 8 | [TestFixture] 9 | public class CharacterRangeTests 10 | { 11 | [Datapoints] 12 | public readonly char[] Datapoints = { char.MinValue, 'a', 'z', 'A', 'Z', char.MaxValue }; 13 | 14 | [Test] 15 | public void Constructor_WhenGivenAMinCharacterGreaterThanTheMaxCharacter_DoesNotThrow() 16 | { 17 | Assert.That(() => new CharacterRange(char.MaxValue, char.MinValue), Throws.Nothing); 18 | } 19 | 20 | [Theory] 21 | public void Equals_WhenConstructedWithTheSameCharacters_ReturnsTrue(char min, char max) 22 | { 23 | var subjectA = new CharacterRange(min, max); 24 | var subjectB = new CharacterRange(min, max); 25 | 26 | Assert.That(subjectA.Equals(subjectB), Is.True); 27 | } 28 | 29 | [Theory] 30 | public void Equals_WithDifferentMaxCharacter_ReturnsFalse(char min, char maxA, char maxB) 31 | { 32 | Assume.That(maxA, Is.Not.EqualTo(maxB)); 33 | 34 | var subjectA = new CharacterRange(min, maxA); 35 | var subjectB = new CharacterRange(min, maxB); 36 | 37 | Assert.That(subjectA.Equals(subjectB), Is.False); 38 | } 39 | 40 | [Theory] 41 | public void Equals_WithDifferentMinCharacter_ReturnsFalse(char minA, char minB, char max) 42 | { 43 | Assume.That(minA, Is.Not.EqualTo(minB)); 44 | 45 | var subjectA = new CharacterRange(minA, max); 46 | var subjectB = new CharacterRange(minB, max); 47 | 48 | Assert.That(subjectA.Equals(subjectB), Is.False); 49 | } 50 | 51 | [Test] 52 | public void Equals_WithNullReference_ReturnsFalse() 53 | { 54 | var subjectA = new CharacterRange(char.MinValue, char.MaxValue); 55 | var subjectB = (object)null; 56 | 57 | Assert.That(subjectA.Equals(subjectB), Is.False); 58 | } 59 | 60 | [Test] 61 | public void Equals_WithOtherObject_ReturnsFalse() 62 | { 63 | var subjectA = new CharacterRange(char.MinValue, char.MaxValue); 64 | var subjectB = new object(); 65 | 66 | Assert.That(subjectA.Equals(subjectB), Is.False); 67 | } 68 | 69 | [Theory] 70 | public void GetHashCode_WhenConstructedWithTheSameCharacters_ReturnsTheSameValue(char min, char max) 71 | { 72 | var subjectA = new CharacterRange(min, max); 73 | var subjectB = new CharacterRange(min, max); 74 | 75 | Assert.That(subjectA.GetHashCode(), Is.EqualTo(subjectB.GetHashCode())); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = crlf 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{cs}] 11 | charset = utf-8-bom 12 | indent_size = 4 13 | dotnet_style_qualification_for_field = true:error 14 | dotnet_style_qualification_for_property = true:error 15 | dotnet_style_qualification_for_method = true:error 16 | dotnet_style_qualification_for_event = true:error 17 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 18 | dotnet_style_predefined_type_for_member_access = true:error 19 | dotnet_style_require_accessibility_modifiers = always:error 20 | dotnet_style_object_initializer = true:suggestion 21 | dotnet_style_collection_initializer = true:warning 22 | dotnet_style_explicit_tuple_names = true:suggestion 23 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 24 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 25 | dotnet_style_coalesce_expression = true:warning 26 | dotnet_style_null_propagation = true:warning 27 | csharp_prefer_braces = true:error 28 | csharp_style_expression_bodied_methods = false:suggestion 29 | csharp_style_expression_bodied_constructors = false:none 30 | csharp_style_expression_bodied_operators = false:none 31 | csharp_style_expression_bodied_properties = true:suggestion 32 | csharp_style_expression_bodied_indexers = true:suggestion 33 | csharp_style_expression_bodied_accessors = true:suggestion 34 | csharp_style_pattern_local_over_anonymous_function = true:warning 35 | csharp_style_var_for_built_in_types = true:error 36 | csharp_style_var_when_type_is_apparent = true:error 37 | csharp_style_var_elsewhere = true:warning 38 | csharp_style_inlined_variable_declaration = true:warning 39 | csharp_style_deconstructed_variable_declaration = true:warning 40 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 41 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 42 | csharp_prefer_simple_default_expression = true:warning 43 | csharp_style_throw_expression = true:warning 44 | csharp_style_conditional_delegate_call = true:warning 45 | dotnet_sort_system_directives_first = true 46 | csharp_new_line_before_open_brace = all 47 | csharp_new_line_before_else = true 48 | csharp_new_line_before_catch = true 49 | csharp_new_line_before_finally = true 50 | csharp_new_line_between_query_expression_clauses = true 51 | csharp_indent_case_contents = true 52 | csharp_indent_switch_labels = true 53 | csharp_indent_labels = one_less_than_current 54 | csharp_space_after_cast = false 55 | csharp_space_after_keywords_in_control_flow_statements = true 56 | csharp_space_between_method_declaration_parameter_list_parentheses = false 57 | csharp_space_between_method_call_parameter_list_parentheses = false 58 | csharp_preserve_single_line_statements = true 59 | csharp_preserve_single_line_blocks = true 60 | 61 | [*.{csproj,xml,yml,yaml,json,config,props,targets,ruleset}] 62 | indent_size = 2 63 | -------------------------------------------------------------------------------- /Pegasus.Tests/Disposable.cs: -------------------------------------------------------------------------------- 1 | // Copyright © John Gietzen. All Rights Reserved. This source is subject to the MIT license. Please see license.md for more information. 2 | 3 | namespace Pegasus.Tests 4 | { 5 | using System; 6 | using System.IO; 7 | 8 | public static class Disposable 9 | { 10 | public static IDisposable FromAction(Action action) 11 | { 12 | return new ActionDisposable(action); 13 | } 14 | 15 | public static IDisposable TempFile(string contents, out string path) 16 | { 17 | IDisposable disposable = null; 18 | try 19 | { 20 | disposable = TempFile(out path); 21 | File.WriteAllText(path, contents); 22 | 23 | var temp = disposable; 24 | disposable = null; 25 | return temp; 26 | } 27 | finally 28 | { 29 | if (disposable != null) 30 | { 31 | disposable.Dispose(); 32 | } 33 | } 34 | } 35 | 36 | public static IDisposable TempFile(out string path) 37 | { 38 | string tempFile = null; 39 | var disposable = FromAction(() => 40 | { 41 | try 42 | { 43 | if (tempFile != null) 44 | { 45 | File.Delete(tempFile); 46 | } 47 | } 48 | catch (IOException) 49 | { 50 | } 51 | }); 52 | 53 | try 54 | { 55 | tempFile = Path.GetTempFileName(); 56 | path = tempFile; 57 | 58 | var temp = disposable; 59 | disposable = null; 60 | return temp; 61 | } 62 | finally 63 | { 64 | if (disposable != null) 65 | { 66 | disposable.Dispose(); 67 | } 68 | } 69 | } 70 | 71 | public static IDisposable TempGeneratedFile(string fileName) 72 | { 73 | return FromAction(() => 74 | { 75 | try 76 | { 77 | File.Delete(fileName); 78 | } 79 | catch (IOException) 80 | { 81 | } 82 | }); 83 | } 84 | 85 | private class ActionDisposable : IDisposable 86 | { 87 | private readonly Action action; 88 | 89 | public ActionDisposable(Action action) 90 | { 91 | this.action = action; 92 | } 93 | 94 | public void Dispose() 95 | { 96 | this.action(); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Pegasus.Package/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Pegasus.Package { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Pegasus.Package.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Pegasus.Package/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | text/microsoft-resx 51 | 52 | 53 | 2.0 54 | 55 | 56 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 57 | 58 | 59 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 60 | 61 | --------------------------------------------------------------------------------