├── .gitignore ├── Directory.Build.props ├── Docs ├── PEG1.png └── Packrat Parser in C.docx ├── LICENSE ├── PEG.Tests ├── ArgumentsTest.cs ├── CaptureTests.cs ├── CodeGrammarTest.cs ├── CppMacro │ ├── Macro.cs │ ├── MacroGrammar.cs │ └── MacroGrammarTest.cs ├── EncloseTest.cs ├── ExpressionExtensionsTest.cs ├── LambdaGrammarTest.cs ├── Lengths │ ├── Length.cs │ └── LengthTests.cs ├── PEG.Tests.csproj ├── ParserTest.cs ├── QutoedStringTests.cs ├── RepeatTests.cs ├── Url │ ├── Url.cs │ ├── UrlGrammar.cs │ └── UrlGrammarTest.cs └── Xml │ └── XmlGrammar.cs ├── PEG.sln ├── PEG.sln.DotSettings ├── PEG.sln.DotSettings.user ├── PEG ├── Builder │ ├── AstAttribute.cs │ ├── ConsumeAttribute.cs │ ├── ConsumeChoice.cs │ ├── ConsumeConditional.cs │ ├── ConsumeExpression.cs │ ├── ConsumeExpressionCache.cs │ ├── ConsumeExpressionParsing.cs │ ├── ConsumeIndexExpression.cs │ ├── ConsumeReferenceExpression.cs │ └── PegBuilder.cs ├── Cst │ ├── CstBuilder.cs │ ├── CstCache.cs │ ├── CstNonterminalNode.cs │ ├── CstString.cs │ ├── ICstNode.cs │ ├── ICstNonterminalNode.cs │ └── ICstTerminalNode.cs ├── ExpressionCompiler.cs ├── ExpressionEnumerator.cs ├── ExpressionExtensions.cs ├── ExpressionWalker.cs ├── Extensions │ ├── AttributeExtensions.cs │ ├── CharExtensions.cs │ ├── EnumerableExtensions.cs │ ├── ReflectionExtensions.cs │ └── StringExtensions.cs ├── FirstSet.cs ├── Grammar.cs ├── GrammarFactory.cs ├── IParseInput.cs ├── IPegBuilder.cs ├── IReplacementTarget.cs ├── LeftRecursion.cs ├── LrParseEngine.cs ├── MemoEntry.cs ├── NonterminalReplacementTarget.cs ├── OutputRecord.cs ├── PEG.csproj ├── ParseEngine.cs ├── ParseError.cs ├── ParseException.cs ├── Peg.cs ├── PegParser.cs ├── Proxies │ ├── Extensions │ │ └── EmitExtensions.cs │ ├── Invocation.cs │ └── Proxy.cs ├── Replacement.cs ├── StringParseInput.cs ├── StringReplacementTarget.cs ├── SyntaxTree │ ├── AndPredicate.cs │ ├── AnyCharacter.cs │ ├── AnyCharacterTerminal.cs │ ├── CharacterRange.cs │ ├── CharacterSet.cs │ ├── CharacterTerminal.cs │ ├── EmptyString.cs │ ├── EncloseExpression.cs │ ├── Expression.cs │ ├── ForeignNonterminal.cs │ ├── IExpressionVisitor.cs │ ├── Nonterminal.cs │ ├── NotPredicate.cs │ ├── NullTerminal.cs │ ├── OneOrMore.cs │ ├── Optional.cs │ ├── OrderedChoice.cs │ ├── Repeat.cs │ ├── Sequence.cs │ ├── Substitution.cs │ ├── Terminal.cs │ ├── Token.cs │ └── ZeroOrMore.cs ├── TokenAttribute.cs ├── TokenParseInput.cs ├── TokenizerAttribute.cs └── Utils │ ├── BitShift.cs │ ├── BooleanSet.cs │ ├── DictionaryList.cs │ ├── DictionarySet.cs │ └── DynamicArray.cs ├── README.md └── appveyor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]in/ 2 | [Oo]bj/ 3 | *.suo 4 | *.DS_Store 5 | **/[Pp]ackages/* 6 | .vs/ 7 | .idea/ 8 | _ReSharper*/ 9 | *.[Rr]e[Ss]harper 10 | *.DotSettings.user 11 | 12 | # NuGet Packages 13 | *.nupkg 14 | # NuGet Symbol Packages 15 | *.snupkg 16 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7.3 5 | 2.0.0 6 | 2.0.0.0 7 | Kirk Woll 8 | 9 | 10 | Copyright 2012-2019 11 | MIT 12 | https://github.com/kswoll/npeg 13 | peg parser 14 | Parsing expression grammar (PEG) DSL for .NET 15 | https://github.com/kswoll/npeg 16 | 17 | 18 | true 19 | true 20 | snupkg 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Docs/PEG1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kswoll/npeg/8b1caa826a868bc298922e0e738a8d2a2de0d61f/Docs/PEG1.png -------------------------------------------------------------------------------- /Docs/Packrat Parser in C.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kswoll/npeg/8b1caa826a868bc298922e0e738a8d2a2de0d61f/Docs/Packrat Parser in C.docx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 kswoll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PEG.Tests/ArgumentsTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace PEG.Tests 4 | { 5 | [TestFixture] 6 | public class ArgumentsTest 7 | { 8 | public class ArgumentsGrammar : Grammar 9 | { 10 | public string Argument { get; set; } 11 | 12 | protected override void OnCreated(object[] args) 13 | { 14 | base.OnCreated(args); 15 | 16 | Argument = (string)args[0]; 17 | } 18 | } 19 | 20 | [Test] 21 | public void Arguments() 22 | { 23 | var grammar = ArgumentsGrammar.Create("foo"); 24 | Assert.AreEqual("foo", grammar.Argument); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG.Tests/CaptureTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG.Tests 5 | { 6 | [TestFixture] 7 | public class CaptureTests 8 | { 9 | [Test] 10 | public void CaptureInitializedProperly() 11 | { 12 | var grammar = TestGrammar.Create(); 13 | var parser = new PegParser(grammar, grammar.Root()); 14 | var input = "AAABBBBB"; 15 | var result = parser.Parse(input); 16 | Assert.AreEqual(input, result.Items); 17 | } 18 | 19 | public class TestData 20 | { 21 | public string Items { get; set; } 22 | } 23 | 24 | public class TestGrammar : Grammar 25 | { 26 | public virtual Expression Root() 27 | { 28 | return Items(); 29 | } 30 | 31 | /* 32 | public virtual Expression A() 33 | { 34 | return 'A'._().Repeat(4, 6); 35 | } 36 | 37 | public virtual Expression B() 38 | { 39 | return 'A'._() | 'B'; 40 | } 41 | */ 42 | 43 | public virtual Expression Items() 44 | { 45 | return -('A'._().Repeat(4, 6).Capture("A") | ('A'._() | 'B').Capture("B")); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /PEG.Tests/CodeGrammarTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG.Tests 5 | { 6 | [TestFixture] 7 | public class CodeGrammarTest 8 | { 9 | public class TestGrammar1 : Grammar 10 | { 11 | public virtual Expression LetterA() 12 | { 13 | return 'a'; 14 | } 15 | 16 | public virtual Expression LetterB() 17 | { 18 | return 'b'; 19 | } 20 | 21 | public virtual Expression LetterChoice() 22 | { 23 | return 'a'._() | 'b'; 24 | } 25 | 26 | public virtual Expression NonterminalAndLetterChoice() 27 | { 28 | return LetterA() | 'b'; 29 | } 30 | 31 | public virtual Expression NonterminalAndNonterminalChoice() 32 | { 33 | return LetterA() | LetterB(); 34 | } 35 | 36 | public virtual Expression LetterSequence() 37 | { 38 | return 'a'._() + 'b'; 39 | } 40 | 41 | public virtual Expression NonterminalAndLetterSequence() 42 | { 43 | return LetterA() + 'b'; 44 | } 45 | 46 | public virtual Expression NonterminalAndNonterminalSequence() 47 | { 48 | return LetterA() + LetterB(); 49 | } 50 | 51 | public virtual Expression NotLetterA() 52 | { 53 | return !LetterA(); 54 | } 55 | 56 | public virtual Expression AndLetterA() 57 | { 58 | return LetterA().And(); 59 | } 60 | 61 | public virtual Expression OneOrMoreLetterA() 62 | { 63 | return +LetterA(); 64 | } 65 | 66 | public virtual Expression ZeroOrMoreLetterA() 67 | { 68 | return -LetterA(); 69 | } 70 | 71 | public virtual Expression OptionalLetterA() 72 | { 73 | return ~LetterA(); 74 | } 75 | 76 | public virtual Expression TwoSequences() 77 | { 78 | return LetterA() + LetterB() | LetterB() + LetterA(); 79 | } 80 | 81 | public virtual Expression ThreeChoices() 82 | { 83 | return LetterA() | LetterB() | 'c'; 84 | } 85 | 86 | public virtual Expression ThreeExpressionSequence() 87 | { 88 | return LetterA() + LetterB() + 'c'; 89 | } 90 | } 91 | 92 | [Test] 93 | public void TestGrammar1Letter() 94 | { 95 | TestGrammar1 grammar = TestGrammar1.Create(); 96 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.LetterA()); 97 | Assert.AreEqual("LetterA", nonterminal.Name); 98 | Assert.AreEqual('a', ((CharacterTerminal)nonterminal.Expression).Character); 99 | } 100 | 101 | [Test] 102 | public void TestGrammar1LetterChoice() 103 | { 104 | TestGrammar1 grammar = TestGrammar1.Create(); 105 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.LetterChoice()); 106 | Assert.AreEqual("LetterChoice", nonterminal.Name); 107 | OrderedChoice orderedChoice = (OrderedChoice)nonterminal.Expression; 108 | Assert.AreEqual('a', ((CharacterTerminal)orderedChoice.Expressions[0]).Character); 109 | Assert.AreEqual('b', ((CharacterTerminal)orderedChoice.Expressions[1]).Character); 110 | } 111 | 112 | [Test] 113 | public void TestGrammar1NonterminalAndLetterChoice() 114 | { 115 | TestGrammar1 grammar = TestGrammar1.Create(); 116 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.NonterminalAndLetterChoice()); 117 | Assert.AreEqual("NonterminalAndLetterChoice", nonterminal.Name); 118 | OrderedChoice orderedChoice = (OrderedChoice)nonterminal.Expression; 119 | Assert.AreEqual("LetterA", ((Nonterminal)orderedChoice.Expressions[0]).Name); 120 | Assert.AreEqual('b', ((CharacterTerminal)orderedChoice.Expressions[1]).Character); 121 | } 122 | 123 | [Test] 124 | public void TestGrammar1NonterminalAndNonterminalChoice() 125 | { 126 | TestGrammar1 grammar = TestGrammar1.Create(); 127 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.NonterminalAndNonterminalChoice()); 128 | Assert.AreEqual("NonterminalAndNonterminalChoice", nonterminal.Name); 129 | OrderedChoice orderedChoice = (OrderedChoice)nonterminal.Expression; 130 | Assert.AreEqual("LetterA", ((Nonterminal)orderedChoice.Expressions[0]).Name); 131 | Assert.AreEqual("LetterB", ((Nonterminal)orderedChoice.Expressions[1]).Name); 132 | } 133 | 134 | [Test] 135 | public void TestGrammar1LetterSequence() 136 | { 137 | TestGrammar1 grammar = TestGrammar1.Create(); 138 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.LetterSequence()); 139 | Assert.AreEqual("LetterSequence", nonterminal.Name); 140 | Sequence orderedChoice = (Sequence)nonterminal.Expression; 141 | Assert.AreEqual('a', ((CharacterTerminal)orderedChoice.Expressions[0]).Character); 142 | Assert.AreEqual('b', ((CharacterTerminal)orderedChoice.Expressions[1]).Character); 143 | } 144 | 145 | [Test] 146 | public void TestGrammar1NonterminalAndLetterSequence() 147 | { 148 | TestGrammar1 grammar = TestGrammar1.Create(); 149 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.NonterminalAndLetterSequence()); 150 | Assert.AreEqual("NonterminalAndLetterSequence", nonterminal.Name); 151 | Sequence orderedChoice = (Sequence)nonterminal.Expression; 152 | Assert.AreEqual("LetterA", ((Nonterminal)orderedChoice.Expressions[0]).Name); 153 | Assert.AreEqual('b', ((CharacterTerminal)orderedChoice.Expressions[1]).Character); 154 | } 155 | 156 | [Test] 157 | public void TestGrammar1NonterminalAndNonterminalSequence() 158 | { 159 | TestGrammar1 grammar = TestGrammar1.Create(); 160 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.NonterminalAndNonterminalSequence()); 161 | Assert.AreEqual("NonterminalAndNonterminalSequence", nonterminal.Name); 162 | Sequence orderedChoice = (Sequence)nonterminal.Expression; 163 | Assert.AreEqual("LetterA", ((Nonterminal)orderedChoice.Expressions[0]).Name); 164 | Assert.AreEqual("LetterB", ((Nonterminal)orderedChoice.Expressions[1]).Name); 165 | } 166 | 167 | [Test] 168 | public void TestGrammar1NotLetterA() 169 | { 170 | TestGrammar1 grammar = TestGrammar1.Create(); 171 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.NotLetterA()); 172 | Assert.AreEqual("NotLetterA", nonterminal.Name); 173 | NotPredicate expression = (NotPredicate)nonterminal.Expression; 174 | Assert.AreEqual("LetterA", ((Nonterminal)expression.Operand).Name); 175 | } 176 | 177 | [Test] 178 | public void TestGrammarAndLetterA() 179 | { 180 | TestGrammar1 grammar = TestGrammar1.Create(); 181 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.AndLetterA()); 182 | Assert.AreEqual("AndLetterA", nonterminal.Name); 183 | AndPredicate expression = (AndPredicate)nonterminal.Expression; 184 | Assert.AreEqual("LetterA", ((Nonterminal)expression.Operand).Name); 185 | } 186 | 187 | [Test] 188 | public void TestGrammarOneOrMoreLetterA() 189 | { 190 | TestGrammar1 grammar = TestGrammar1.Create(); 191 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.OneOrMoreLetterA()); 192 | Assert.AreEqual("OneOrMoreLetterA", nonterminal.Name); 193 | OneOrMore expression = (OneOrMore)nonterminal.Expression; 194 | Assert.AreEqual("LetterA", ((Nonterminal)expression.Operand).Name); 195 | } 196 | 197 | [Test] 198 | public void TestGrammarZeroOrMoreLetterA() 199 | { 200 | TestGrammar1 grammar = TestGrammar1.Create(); 201 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.ZeroOrMoreLetterA()); 202 | Assert.AreEqual("ZeroOrMoreLetterA", nonterminal.Name); 203 | ZeroOrMore expression = (ZeroOrMore)nonterminal.Expression; 204 | Assert.AreEqual("LetterA", ((Nonterminal)expression.Operand).Name); 205 | } 206 | 207 | [Test] 208 | public void TestGrammarOptionalLetterA() 209 | { 210 | TestGrammar1 grammar = TestGrammar1.Create(); 211 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.OptionalLetterA()); 212 | Assert.AreEqual("OptionalLetterA", nonterminal.Name); 213 | Optional expression = (Optional)nonterminal.Expression; 214 | Assert.AreEqual("LetterA", ((Nonterminal)expression.Operand).Name); 215 | } 216 | 217 | [Test] 218 | public void TestGrammarTwoSequences() 219 | { 220 | TestGrammar1 grammar = TestGrammar1.Create(); 221 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.TwoSequences()); 222 | Assert.AreEqual("TwoSequences", nonterminal.Name); 223 | OrderedChoice orderedChoice = (OrderedChoice)nonterminal.Expression; 224 | Sequence sequence1 = (Sequence)orderedChoice.Expressions[0]; 225 | Sequence sequence2 = (Sequence)orderedChoice.Expressions[1]; 226 | Assert.AreEqual("LetterA", ((Nonterminal)sequence1.Expressions[0]).Name); 227 | Assert.AreEqual("LetterB", ((Nonterminal)sequence1.Expressions[1]).Name); 228 | Assert.AreEqual("LetterB", ((Nonterminal)sequence2.Expressions[0]).Name); 229 | Assert.AreEqual("LetterA", ((Nonterminal)sequence2.Expressions[1]).Name); 230 | } 231 | 232 | [Test] 233 | public void TestGrammarThreeChoices() 234 | { 235 | TestGrammar1 grammar = TestGrammar1.Create(); 236 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.ThreeChoices()); 237 | Assert.AreEqual("ThreeChoices", nonterminal.Name); 238 | OrderedChoice orderedChoice = (OrderedChoice)nonterminal.Expression; 239 | Assert.AreEqual("LetterA", ((Nonterminal)orderedChoice.Expressions[0]).Name); 240 | Assert.AreEqual("LetterB", ((Nonterminal)orderedChoice.Expressions[1]).Name); 241 | Assert.AreEqual('c', ((CharacterTerminal)orderedChoice.Expressions[2]).Character); 242 | } 243 | 244 | [Test] 245 | public void TestGrammarThreeExpressionSequence() 246 | { 247 | TestGrammar1 grammar = TestGrammar1.Create(); 248 | Nonterminal nonterminal = grammar.GetNonterminal(o => o.ThreeExpressionSequence()); 249 | Assert.AreEqual("ThreeExpressionSequence", nonterminal.Name); 250 | Sequence sequence = (Sequence)nonterminal.Expression; 251 | Assert.AreEqual("LetterA", ((Nonterminal)sequence.Expressions[0]).Name); 252 | Assert.AreEqual("LetterB", ((Nonterminal)sequence.Expressions[1]).Name); 253 | Assert.AreEqual('c', ((CharacterTerminal)sequence.Expressions[2]).Character); 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /PEG.Tests/CppMacro/Macro.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using PEG.Builder; 4 | 5 | namespace PEG.Tests.CppMacro 6 | { 7 | [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] 8 | [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] 9 | [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] 10 | [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] 11 | public class Macro 12 | { 13 | private const string NullString = ""; 14 | private static readonly PegParser parser = new PegParser(MacroGrammar.Create()); 15 | public OrModel Or { get; set; } 16 | 17 | public static Macro Parse(string macro) 18 | { 19 | return parser.Parse(macro); 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return Or?.ToString() ?? NullString; 25 | } 26 | 27 | public class AndModel 28 | { 29 | [Consume] public List PrimitiveCondition { get; set; } 30 | 31 | public override string ToString() 32 | { 33 | var value = string.Join(" && ", PrimitiveCondition); 34 | return PrimitiveCondition.Count > 1 ? $"({value})" : value; 35 | } 36 | } 37 | 38 | public class OrModel 39 | { 40 | [Consume] public List And { get; set; } 41 | 42 | public override string ToString() 43 | { 44 | var value = string.Join(" || ", And); 45 | return And.Count > 1 ? $"({value})" : value; 46 | } 47 | } 48 | 49 | public class PrimitiveConditionModel 50 | { 51 | public OrModel Or { get; set; } 52 | public NegateConditionModel NegateCondition { get; set; } 53 | public DefinedModel Defined { get; set; } 54 | public string Identifier { get; set; } 55 | 56 | public override string ToString() 57 | { 58 | return Identifier ?? Defined?.ToString() ?? NegateCondition?.ToString() ?? Or?.ToString() ?? NullString; 59 | } 60 | } 61 | 62 | public class DefinedModel 63 | { 64 | public IdentifierWrapModel IdentifierWrap { get; set; } 65 | 66 | public override string ToString() 67 | { 68 | return $"defined({IdentifierWrap})"; 69 | } 70 | } 71 | 72 | public class NegateConditionModel 73 | { 74 | public OrModel Or { get; set; } 75 | 76 | public override string ToString() 77 | { 78 | return $"!({Or})"; 79 | } 80 | } 81 | 82 | public class IdentifierWrapModel 83 | { 84 | public IdentifierWrapModel IdentifierWrap { get; set; } 85 | public string Identifier { get; set; } 86 | 87 | public override string ToString() 88 | { 89 | return Identifier ?? IdentifierWrap?.ToString() ?? NullString; 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /PEG.Tests/CppMacro/MacroGrammar.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG.Tests.CppMacro 5 | { 6 | [SuppressMessage("ReSharper", "FunctionRecursiveOnAllPaths")] 7 | public class MacroGrammar : Grammar 8 | { 9 | /// 10 | /// You should always make the constructor private so that you never accidentally instantiate it directly. 11 | /// Always use the .Create() method inherited from Grammar. i.e. MacroGrammar.Create(); 12 | /// 13 | private MacroGrammar() 14 | { 15 | } 16 | 17 | public virtual Expression Root() 18 | { 19 | return Or(); 20 | } 21 | 22 | public virtual Expression And() 23 | { 24 | return PrimitiveCondition() + -("&&" + PrimitiveCondition()); 25 | } 26 | 27 | public virtual Expression Or() 28 | { 29 | return And() + -("||" + And()); 30 | } 31 | 32 | public virtual Expression PrimitiveCondition() 33 | { 34 | return ~Space() + (('(' + Or() + ')') | Defined() | NegateCondition() | Identifier()) + ~Space(); 35 | } 36 | 37 | public virtual Expression NegateCondition() 38 | { 39 | return '!' + Or(); 40 | } 41 | 42 | public virtual Expression Defined() 43 | { 44 | return ~Space() + "defined" + IdentifierWrap(); 45 | } 46 | 47 | public virtual Expression IdentifierWrap() 48 | { 49 | return ~Space() + (('(' + IdentifierWrap() + ')') | Identifier()) + ~Space(); 50 | } 51 | 52 | public virtual Expression Identifier() 53 | { 54 | return IdentifierStartChar() + -IdentifierChar(); 55 | } 56 | 57 | public virtual Expression IdentifierChar() 58 | { 59 | return IdentifierStartChar() | '0'.To('9'); 60 | } 61 | 62 | public virtual Expression IdentifierStartChar() 63 | { 64 | return 'a'.To('Z') | '_'; 65 | } 66 | 67 | public virtual Expression Space() 68 | { 69 | return +(' '._() | '\r' | '\n' | '\t'); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /PEG.Tests/CppMacro/MacroGrammarTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace PEG.Tests.CppMacro 4 | { 5 | [TestFixture] 6 | public class MacroGrammarTest 7 | { 8 | [Test] 9 | public void AndBuiltInIdentifier() 10 | { 11 | var macro = Macro.Parse("ASMJIT_CXX_CLANG && defined(__has_builtin)"); 12 | Assert.AreEqual("(ASMJIT_CXX_CLANG && defined(__has_builtin))", macro.ToString()); 13 | } 14 | 15 | [Test] 16 | public void AndNot() 17 | { 18 | var macro = Macro.Parse("defined(ASMJIT_NO_TEXT) && !defined(ASMJIT_NO_LOGGING)"); 19 | Assert.AreEqual("(defined(ASMJIT_NO_TEXT) && !(defined(ASMJIT_NO_LOGGING)))", macro.ToString()); 20 | } 21 | 22 | [Test] 23 | public void AndNotOr() 24 | { 25 | var macro = Macro.Parse( 26 | "(defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM)) && !defined(MIDL_PASS)" 27 | ); 28 | Assert.AreEqual( 29 | "((defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM)) && !(defined(MIDL_PASS)))", 30 | macro.ToString() 31 | ); 32 | } 33 | 34 | [Test] 35 | public void BuiltInIdentifier() 36 | { 37 | var macro = Macro.Parse("__cplusplus"); 38 | Assert.AreEqual("__cplusplus", macro.ToString()); 39 | } 40 | 41 | [Test] 42 | public void Defined() 43 | { 44 | var macro = Macro.Parse("defined(_WIN32)"); 45 | Assert.AreEqual("defined(_WIN32)", macro.ToString()); 46 | } 47 | 48 | [Test] 49 | public void DefinedSpace() 50 | { 51 | var macro = Macro.Parse("defined (_WIN64)"); 52 | Assert.AreEqual("defined(_WIN64)", macro.ToString()); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /PEG.Tests/EncloseTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using static PEG.Peg; 3 | 4 | namespace PEG.Tests 5 | { 6 | [TestFixture] 7 | public class EncloseTest 8 | { 9 | [Test] 10 | public void SimpleQuotes() 11 | { 12 | Assert.IsTrue('"'.Enclose(-Any).Match("\"Hello\"")); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /PEG.Tests/ExpressionExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using NUnit.Framework; 6 | 7 | namespace PEG.Tests 8 | { 9 | [TestFixture] 10 | public class ExpressionExtensionsTest 11 | { 12 | [Test] 13 | public void MatchString() 14 | { 15 | var pattern = +('a'.To('Z') | '.') + '@' + +('a'.To('Z') | '.'); 16 | 17 | Assert.IsTrue(pattern.Match("john@test.com")); 18 | Assert.IsFalse(pattern.Match("john@test@com")); 19 | } 20 | 21 | [Test] 22 | public void ContainsString() 23 | { 24 | var pattern = +('a'.To('Z') | '.') + '@' + +('a'.To('Z') | '.'); 25 | 26 | var pattern2 = +'0'.To('9'); 27 | pattern2.Match("42"); 28 | 29 | Assert.IsTrue(pattern.Contains("asdf john@test.com asfd asdf asdf asdf")); 30 | Assert.IsFalse(pattern.Contains("asdf john%test%com")); 31 | } 32 | 33 | [Test] 34 | public void Test() 35 | { 36 | var pattern = '"' + +(!('"'._() | @"\") + Peg.Any | @"\\" | @"\""") + '"'; 37 | 38 | Assert.IsTrue(pattern.Match("\"value\"")); 39 | Assert.IsTrue(pattern.Match("\"the \\\"quote\\\"\"")); 40 | } 41 | 42 | /// 43 | /// Replace the month and day components. 44 | /// 45 | [Test] 46 | public void Replace() 47 | { 48 | var digit = ('0').To('9'); 49 | var month = digit.Repeat(1, 2).Capture(); 50 | var day = digit.Repeat(1, 2).Capture(); 51 | var pattern = month + '/' + day + '/' + digit.Repeat(4); 52 | 53 | const string input = "12/1/2004"; 54 | string result = pattern.Transform(input, month.To(day), day.To(month)); 55 | 56 | Assert.AreEqual("1/12/2004", result); 57 | } 58 | 59 | /// 60 | /// Replace the month and day components. 61 | /// 62 | [Test] 63 | public void ReplaceAll() 64 | { 65 | var digit = ('0').To('9'); 66 | var month = digit.Repeat(1, 2).Capture(); 67 | var day = digit.Repeat(1, 2).Capture(); 68 | var pattern = month + '/' + day + '/' + digit.Repeat(4); 69 | 70 | const string input = "12/1/2004 and 2/8/1988"; 71 | 72 | string result = pattern.Replace(input, month.To(day), day.To(month)); 73 | 74 | Assert.AreEqual("1/12/2004 and 8/2/1988", result); 75 | } 76 | 77 | [Test] 78 | public void ReplaceToString() 79 | { 80 | var digit = ('0').To('9'); 81 | var month = digit.Repeat(1, 2).Capture(); 82 | var day = digit.Repeat(1, 2).Capture(); 83 | var pattern = month + '/' + day + '/' + digit.Repeat(4); 84 | 85 | const string input = "12/1/2004 and 2/8/1988"; 86 | 87 | string result = pattern.Replace(input, month.To("MM")); 88 | 89 | Assert.AreEqual("MM/1/2004 and MM/8/1988", result); 90 | } 91 | 92 | [Test] 93 | public void Length() 94 | { 95 | var number = +'0'.To('9'); 96 | var feet = number.Capture(); 97 | var inches = number.Capture(); 98 | var pattern = feet + '\'' + ~(-' '._() + inches + '\"'); 99 | 100 | var results = pattern.Parse("5' 6\""); 101 | int feetResult = int.Parse(results[feet]); 102 | int inchesResult = int.Parse(results[inches]); 103 | 104 | Assert.AreEqual(5, feetResult); 105 | Assert.AreEqual(6, inchesResult); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /PEG.Tests/LambdaGrammarTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace PEG.Tests 4 | { 5 | [TestFixture] 6 | public class LambdaGrammarTest 7 | { 8 | [Test] 9 | public void Integer() 10 | { 11 | bool success = (+('0'.To('9'))).Match("12345"); 12 | Assert.IsTrue(success); 13 | } 14 | 15 | [Test] 16 | public void Decimal() 17 | { 18 | var i = +('0'.To('9')); 19 | var d = i + ~('.' + i); 20 | 21 | Assert.IsTrue(d.Match("12345")); 22 | Assert.IsTrue(d.Match("1.2")); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /PEG.Tests/Lengths/Length.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG.Samples.Lengths 5 | { 6 | public struct Length 7 | { 8 | public decimal Inches { get; private set; } 9 | 10 | private static LengthGrammar grammar = LengthGrammar.Create(); 11 | private static PegParser parser = new PegParser(grammar, grammar.Start()); 12 | 13 | public Length(decimal inches) : this() 14 | { 15 | Inches = inches; 16 | } 17 | 18 | public Length(int feet, decimal inches) : this() 19 | { 20 | Inches = feet * 12 + inches; 21 | } 22 | 23 | public decimal Feet 24 | { 25 | get { return Inches / 12; } 26 | } 27 | 28 | /// 29 | /// Valid measurement patterns: 30 | /// 31 | /// X' Y.Z" (feet, then inches in decimal) 32 | /// X'Y.Z" (feet, then inches in decimal without any space) 33 | /// X' Y.Z" (feet, then inches in decimal, multiple spaces) 34 | /// X.Z' (feet in decimal) 35 | /// Y.Z" (inches in decimal) 36 | /// X'Y" (feet, then inches) 37 | /// Y" (inches) 38 | /// 39 | public static bool TryParse(string s, out Length result) 40 | { 41 | ParsedLength parsedLength; 42 | int amountRead; 43 | if (!parser.Parse(s.Trim(), out parsedLength, out amountRead)) 44 | { 45 | result = default(Length); 46 | return false; 47 | } 48 | result = 49 | parsedLength.IntegerFeet > 0 ? new Length(parsedLength.IntegerFeet, parsedLength.Inches) : 50 | parsedLength.DecimalFeet > 0 ? new Length(parsedLength.DecimalFeet * 12) : 51 | new Length(parsedLength.Inches); 52 | return true; 53 | } 54 | 55 | public static Length Parse(string s) 56 | { 57 | Length result; 58 | if (TryParse(s, out result)) 59 | return result; 60 | else 61 | throw new Exception("Unable to parse length: " + s); 62 | } 63 | 64 | public class ParsedLength 65 | { 66 | public int IntegerFeet { get; set; } 67 | public decimal DecimalFeet { get; set; } 68 | public decimal Inches { get; set; } 69 | } 70 | 71 | public class LengthGrammar : Grammar 72 | { 73 | public virtual Expression Start() 74 | { 75 | return 76 | IntegerFeet() + OptionalWhitespace() + '\'' + OptionalWhitespace() + Inches() + OptionalWhitespace() + '"' | 77 | DecimalFeet() + OptionalWhitespace() + '\'' | 78 | Inches() + OptionalWhitespace() + '"'; 79 | } 80 | 81 | public virtual Expression IntegerFeet() 82 | { 83 | return Integer(); 84 | } 85 | 86 | public virtual Expression DecimalFeet() 87 | { 88 | return Decimal(); 89 | } 90 | 91 | public virtual Expression Inches() 92 | { 93 | return Decimal(); 94 | } 95 | 96 | public virtual Expression OptionalWhitespace() 97 | { 98 | return -' '._(); 99 | } 100 | 101 | public virtual Expression Integer() 102 | { 103 | return +'0'.To('9'); 104 | } 105 | 106 | public virtual Expression Decimal() 107 | { 108 | return Integer() + ~('.' + Integer()); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /PEG.Tests/Lengths/LengthTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace PEG.Samples.Lengths 4 | { 5 | [TestFixture] 6 | public class LengthTests 7 | { 8 | [Test] 9 | public void IntegerDecimal() 10 | { 11 | var s = "5.5'"; 12 | var length = Length.Parse(s); 13 | Assert.AreEqual(5.5, length.Feet); 14 | } 15 | 16 | [Test] 17 | public void InchesInteger() 18 | { 19 | var s = "5\""; 20 | var length = Length.Parse(s); 21 | Assert.AreEqual(5, length.Inches); 22 | } 23 | 24 | [Test] 25 | public void FeetAndInchesInteger() 26 | { 27 | var s = "5' 4\""; 28 | var length = Length.Parse(s); 29 | Assert.AreEqual(64, length.Inches); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /PEG.Tests/PEG.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PEG.Tests/ParserTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG.Tests 5 | { 6 | [TestFixture] 7 | public class ParserTest 8 | { 9 | public class TestGrammar1 : Grammar 10 | { 11 | public virtual Expression LetterA() 12 | { 13 | return 'a'; 14 | } 15 | 16 | public virtual Expression LetterB() 17 | { 18 | return 'b'; 19 | } 20 | 21 | public virtual Expression LetterChoice() 22 | { 23 | return 'a'._() | 'b'; 24 | } 25 | 26 | public virtual Expression NonterminalAndLetterChoice() 27 | { 28 | return LetterA() | 'b'; 29 | } 30 | 31 | public virtual Expression NonterminalAndNonterminalChoice() 32 | { 33 | return LetterA() | LetterB(); 34 | } 35 | 36 | public virtual Expression LetterSequence() 37 | { 38 | return 'a'._() + 'b'; 39 | } 40 | 41 | public virtual Expression NonterminalAndLetterSequence() 42 | { 43 | return LetterA() + 'b'; 44 | } 45 | 46 | public virtual Expression NonterminalAndNonterminalSequence() 47 | { 48 | return LetterA() + LetterB(); 49 | } 50 | 51 | public virtual Expression NotLetterA() 52 | { 53 | return !LetterA(); 54 | } 55 | 56 | public virtual Expression AndLetterA() 57 | { 58 | return LetterA().And(); 59 | } 60 | 61 | public virtual Expression OneOrMoreLetterA() 62 | { 63 | return +LetterA(); 64 | } 65 | 66 | public virtual Expression ZeroOrMoreLetterA() 67 | { 68 | return -LetterA(); 69 | } 70 | 71 | public virtual Expression OptionalLetterA() 72 | { 73 | return ~LetterA(); 74 | } 75 | 76 | public virtual Expression TwoSequences() 77 | { 78 | return LetterA() + LetterB() | LetterB() + LetterA(); 79 | } 80 | 81 | public virtual Expression ThreeChoices() 82 | { 83 | return LetterA() | LetterB() | 'c'; 84 | } 85 | 86 | public virtual Expression ThreeExpressionSequence() 87 | { 88 | return LetterA() + LetterB() + 'c'; 89 | } 90 | 91 | public virtual Expression LeftRecursion() 92 | { 93 | return LeftRecursion() + LetterA() | LetterA(); 94 | } 95 | } 96 | 97 | [Test] 98 | public void LetterA() 99 | { 100 | TestGrammar1 grammar = TestGrammar1.Create(); 101 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.LetterA())); 102 | Assert.IsNotNull(parser.ParseString("a")); 103 | Assert.IsNull(parser.ParseString("b")); 104 | } 105 | 106 | [Test] 107 | public void LetterChoice() 108 | { 109 | TestGrammar1 grammar = TestGrammar1.Create(); 110 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.LetterChoice())); 111 | Assert.IsNotNull(parser.ParseString("a")); 112 | Assert.IsNotNull(parser.ParseString("b")); 113 | Assert.IsNull(parser.ParseString("c")); 114 | } 115 | 116 | [Test] 117 | public void NonterminalAndLetterChoice() 118 | { 119 | TestGrammar1 grammar = TestGrammar1.Create(); 120 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.NonterminalAndLetterChoice())); 121 | Assert.IsNotNull(parser.ParseString("a")); 122 | Assert.IsNotNull(parser.ParseString("b")); 123 | Assert.IsNull(parser.ParseString("c")); 124 | } 125 | 126 | [Test] 127 | public void NonterminalAndNonterminalChoice() 128 | { 129 | TestGrammar1 grammar = TestGrammar1.Create(); 130 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.NonterminalAndNonterminalChoice())); 131 | Assert.IsNotNull(parser.ParseString("a")); 132 | Assert.IsNotNull(parser.ParseString("b")); 133 | Assert.IsNull(parser.ParseString("c")); 134 | } 135 | 136 | [Test] 137 | public void LetterSequence() 138 | { 139 | TestGrammar1 grammar = TestGrammar1.Create(); 140 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.LetterSequence())); 141 | Assert.IsNotNull(parser.ParseString("ab")); 142 | Assert.IsNull(parser.ParseString("a")); 143 | Assert.IsNull(parser.ParseString("b")); 144 | } 145 | 146 | [Test] 147 | public void NotLetterA() 148 | { 149 | TestGrammar1 grammar = TestGrammar1.Create(); 150 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.NotLetterA())); 151 | Assert.IsNull(parser.ParseString("a")); 152 | Assert.IsNotNull(parser.ParseString("b", false)); 153 | } 154 | 155 | [Test] 156 | public void AndLetterA() 157 | { 158 | TestGrammar1 grammar = TestGrammar1.Create(); 159 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.AndLetterA())); 160 | Assert.IsNotNull(parser.ParseString("a", false)); 161 | Assert.IsNull(parser.ParseString("b")); 162 | } 163 | 164 | [Test] 165 | public void OneOrMoreLetterA() 166 | { 167 | TestGrammar1 grammar = TestGrammar1.Create(); 168 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.OneOrMoreLetterA())); 169 | Assert.IsNotNull(parser.ParseString("a")); 170 | Assert.IsNotNull(parser.ParseString("aa")); 171 | Assert.IsNotNull(parser.ParseString("aaa")); 172 | Assert.IsNull(parser.ParseString("b")); 173 | Assert.IsNull(parser.ParseString("")); 174 | } 175 | 176 | [Test] 177 | public void ZeroOrMoreLetterA() 178 | { 179 | TestGrammar1 grammar = TestGrammar1.Create(); 180 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.ZeroOrMoreLetterA())); 181 | Assert.IsNotNull(parser.ParseString("")); 182 | Assert.IsNotNull(parser.ParseString("a")); 183 | Assert.IsNotNull(parser.ParseString("aa")); 184 | Assert.IsNotNull(parser.ParseString("aaa")); 185 | } 186 | 187 | [Test] 188 | public void LeftRecursion() 189 | { 190 | TestGrammar1 grammar = TestGrammar1.Create(); 191 | PegParser parser = new PegParser(grammar, grammar.GetNonterminal(o => o.LeftRecursion())); 192 | Assert.IsNotNull(parser.ParseString("a")); 193 | Assert.IsNotNull(parser.ParseString("aa")); 194 | Assert.IsNotNull(parser.ParseString("aaa")); 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /PEG.Tests/QutoedStringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace PEG.Tests 5 | { 6 | [TestFixture] 7 | public class QutoedStringTests 8 | { 9 | [Test] 10 | public void EscapedQuote() 11 | { 12 | var body = (-(!('"'._() | @"\") + Peg.Any | @"\\" | @"\""")).Capture(); 13 | var pattern = '"' + body + '"'; 14 | 15 | Console.WriteLine(pattern.Parse("\"abc\"")[body]); 16 | Console.WriteLine(pattern.Parse("\"a\\\"bc\"")[body]); 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /PEG.Tests/RepeatTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG.Tests 6 | { 7 | [TestFixture] 8 | public class RepeatTests 9 | { 10 | [Test] 11 | public void RepeatResets() 12 | { 13 | var grammar = TestGrammar.Create(); 14 | var parser = new PegParser(grammar, grammar.Root()); 15 | var input = "AAABBBBB"; 16 | var result = parser.Parse(input); 17 | Assert.AreEqual(input, result.Items); 18 | } 19 | 20 | public class TestData 21 | { 22 | public string Items { get; set; } 23 | } 24 | 25 | public class TestGrammar : Grammar 26 | { 27 | public virtual Expression Root() 28 | { 29 | return Items(); 30 | } 31 | 32 | public virtual Expression Items() 33 | { 34 | return -(A() | B()); 35 | } 36 | 37 | public virtual Expression A() 38 | { 39 | return 'A'._().Repeat(4, 6); 40 | } 41 | 42 | public virtual Expression B() 43 | { 44 | return 'A'._() | 'B'; 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /PEG.Tests/Url/Url.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace PEG.Samples.Url 5 | { 6 | public class Url 7 | { 8 | public string Protocol { get; set; } 9 | public string Domain { get; set; } 10 | public string Port { get; set; } 11 | public string Path { get; set; } 12 | public List QueryString { get; set; } 13 | 14 | private static PegParser parser = new PegParser(UrlGrammar.Create()); 15 | 16 | public static Url Parse(string url) 17 | { 18 | return parser.Parse(url); 19 | } 20 | 21 | public override string ToString() 22 | { 23 | StringBuilder builder = new StringBuilder(Protocol + "://" + Domain); 24 | if (Port != null) 25 | builder.Append(':' + Port); 26 | if (Path != null) 27 | builder.Append(Path); 28 | if (QueryString != null) 29 | { 30 | builder.Append('?'); 31 | for (int i = 0; i < QueryString.Count; i++) 32 | { 33 | var nameValue = QueryString[i]; 34 | builder.Append(nameValue.Name); 35 | builder.Append('='); 36 | builder.Append(nameValue.Value); 37 | if (i < QueryString.Count - 1) 38 | builder.Append('&'); 39 | } 40 | } 41 | return builder.ToString(); 42 | } 43 | 44 | public class NameValue 45 | { 46 | public string Name { get; set; } 47 | public string Value { get; set; } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /PEG.Tests/Url/UrlGrammar.cs: -------------------------------------------------------------------------------- 1 | using PEG.SyntaxTree; 2 | 3 | namespace PEG.Samples.Url 4 | { 5 | /// 6 | /// Provides the grammar for a URL 7 | /// 8 | public class UrlGrammar : Grammar 9 | { 10 | /// 11 | /// You should always make the constructor private so that you never accidentally instantiate it directly. 12 | /// Always use the .Create() method inherited from Grammar. i.e. UrlGrammar.Create(); 13 | /// 14 | private UrlGrammar() 15 | { 16 | } 17 | 18 | public virtual Expression Url() 19 | { 20 | return Protocol() + "://" + Domain() + ~(':' + Port()) + ~(Path() + ~('?' + QueryString())); 21 | } 22 | 23 | public virtual Expression Protocol() 24 | { 25 | return "http"._() | "https"; 26 | } 27 | 28 | public virtual Expression Domain() 29 | { 30 | return Domain() + '.' + DomainWord() | DomainWord(); 31 | } 32 | 33 | public virtual Expression DomainWord() 34 | { 35 | return +('a'.To('z') | 'A'.To('Z') | '0'.To('9') | '-'); 36 | } 37 | 38 | public virtual Expression Port() 39 | { 40 | return +('0'.To('9')); 41 | } 42 | 43 | public virtual Expression Path() 44 | { 45 | return +('/' + +('a'.To('z') | 'A'.To('Z') | '0'.To('9') | '.' | '_')); 46 | } 47 | 48 | public virtual Expression QueryString() 49 | { 50 | return NameValue() + -('&' + NameValue()); 51 | } 52 | 53 | public virtual Expression NameValue() 54 | { 55 | return Name() + '=' + Value(); 56 | } 57 | 58 | public virtual Expression Name() 59 | { 60 | return +('a'.To('z') | 'A'.To('Z') | '0'.To('9')); 61 | } 62 | 63 | public virtual Expression Value() 64 | { 65 | return -('a'.To('z') | 'A'.To('Z') | '0'.To('9') | '%'); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /PEG.Tests/Url/UrlGrammarTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace PEG.Samples.Url 4 | { 5 | [TestFixture] 6 | public class UrlGrammarTest 7 | { 8 | [Test] 9 | public void JustDomain() 10 | { 11 | Url url = Url.Parse("http://test.com"); 12 | Assert.AreEqual("test.com", url.Domain); 13 | } 14 | 15 | [Test] 16 | public void DomainAndPort() 17 | { 18 | Url url = Url.Parse("http://test.com:433"); 19 | Assert.AreEqual("test.com", url.Domain); 20 | Assert.AreEqual("433", url.Port); 21 | } 22 | 23 | [Test] 24 | public void DomainAndPortAndFile() 25 | { 26 | Url url = Url.Parse("http://test.com:433/Test.txt"); 27 | Assert.AreEqual("test.com", url.Domain); 28 | Assert.AreEqual("433", url.Port); 29 | Assert.AreEqual("/Test.txt", url.Path); 30 | } 31 | 32 | [Test] 33 | public void DomainAndFile() 34 | { 35 | Url url = Url.Parse("http://test.com/Test.txt"); 36 | Assert.AreEqual("test.com", url.Domain); 37 | Assert.AreEqual("/Test.txt", url.Path); 38 | } 39 | 40 | [Test] 41 | public void DomainAndFileQueryString() 42 | { 43 | Url url = Url.Parse("http://test.com/Test.txt?name1=value1"); 44 | Assert.AreEqual("test.com", url.Domain); 45 | Assert.AreEqual("/Test.txt", url.Path); 46 | Assert.AreEqual("name1", url.QueryString[0].Name); 47 | Assert.AreEqual("value1", url.QueryString[0].Value); 48 | } 49 | 50 | [Test] 51 | public void DomainAndFileQueryString2() 52 | { 53 | Url url = Url.Parse("http://test.com/Test.txt?name1=value1&name2=value2"); 54 | Assert.AreEqual("test.com", url.Domain); 55 | Assert.AreEqual("/Test.txt", url.Path); 56 | Assert.AreEqual("name1", url.QueryString[0].Name); 57 | Assert.AreEqual("value1", url.QueryString[0].Value); 58 | Assert.AreEqual("name2", url.QueryString[1].Name); 59 | Assert.AreEqual("value2", url.QueryString[1].Value); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /PEG.Tests/Xml/XmlGrammar.cs: -------------------------------------------------------------------------------- 1 | using PEG.SyntaxTree; 2 | 3 | namespace PEG.Samples.Xml 4 | { 5 | public class XmlGrammar : Grammar 6 | { 7 | private XmlGrammar() 8 | { 9 | } 10 | 11 | public virtual Expression Symbol() 12 | { 13 | return Element() | 14 | Text(); 15 | } 16 | 17 | public virtual Expression Element() 18 | { 19 | return ElementStart() + -Symbol() + ElementEnd() | 20 | ElementClosed(); 21 | } 22 | 23 | public virtual Expression ElementStart() 24 | { 25 | return '<' + ElementHead() + '>'; 26 | } 27 | 28 | public virtual Expression ElementClosed() 29 | { 30 | return '<' + ElementHead() + "/>"; 31 | } 32 | 33 | public virtual Expression ElementEnd() 34 | { 35 | return "'; 36 | } 37 | 38 | public virtual Expression Text() 39 | { 40 | return !'<'._() + Any; 41 | } 42 | 43 | public virtual Expression ElementHead() 44 | { 45 | return ~WS() + QualifiableName() + Attributes() + ~WS(); 46 | } 47 | 48 | public virtual Expression Attributes() 49 | { 50 | return -Attribute(); 51 | } 52 | 53 | public virtual Expression Attribute() 54 | { 55 | return ' ' + QualifiableName() + ~WS() + '=' + ~WS() + AttributeValuePart(); 56 | } 57 | 58 | public virtual Expression AttributeValuePart() 59 | { 60 | return '"' + DoubleQuoteAttributeValue() + '"' | 61 | '\'' + SingleQuoteAttributeValue() + '\''; 62 | } 63 | 64 | public virtual Expression DoubleQuoteAttributeValue() 65 | { 66 | return -DoubleQuoteAttributeValueChar(); 67 | } 68 | 69 | public virtual Expression SingleQuoteAttributeValue() 70 | { 71 | return -SingleQuoteAttributeValueChar(); 72 | } 73 | 74 | public virtual Expression DoubleQuoteAttributeValueChar() 75 | { 76 | return !('"'._() | '\\') + Any | "\\\"" | "\\\\"; 77 | } 78 | 79 | public virtual Expression SingleQuoteAttributeValueChar() 80 | { 81 | return !('\''._() | '\\') + Any | "\\\'" | "\\\\"; 82 | } 83 | 84 | public virtual Expression QualifiableName() 85 | { 86 | return ~(Prefix() + ':') + Identifier(); 87 | } 88 | 89 | public virtual Expression Prefix() 90 | { 91 | return Identifier(); 92 | } 93 | 94 | public virtual Expression Identifier() 95 | { 96 | return IdentifierStartChar() + -IdentifierChar(); 97 | } 98 | 99 | public virtual Expression IdentifierChar() 100 | { 101 | return IdentifierStartChar() | '0'.To('9') | '-'; 102 | } 103 | 104 | public virtual Expression IdentifierStartChar() 105 | { 106 | return 'a'.To('Z'); 107 | } 108 | 109 | public virtual Expression WS() 110 | { 111 | return +(' '._() | '\r' | '\n' | '\t'); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /PEG.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29020.237 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PEG", "PEG\PEG.csproj", "{716F258F-FEC8-4836-AE98-151727E584E8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PEG.Tests", "PEG.Tests\PEG.Tests.csproj", "{572B9375-2FE3-489F-86E1-425E39AC6D74}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {716F258F-FEC8-4836-AE98-151727E584E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {716F258F-FEC8-4836-AE98-151727E584E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {716F258F-FEC8-4836-AE98-151727E584E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {716F258F-FEC8-4836-AE98-151727E584E8}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {572B9375-2FE3-489F-86E1-425E39AC6D74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {572B9375-2FE3-489F-86E1-425E39AC6D74}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {572B9375-2FE3-489F-86E1-425E39AC6D74}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {572B9375-2FE3-489F-86E1-425E39AC6D74}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8914B0DE-3A4A-4279-BB04-2F211C866EB3} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /PEG.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | DO_NOT_SHOW 7 | DO_NOT_SHOW 8 | DO_NOT_SHOW 9 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -------------------------------------------------------------------------------- /PEG.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /PEG/Builder/AstAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG.Builder 4 | { 5 | public enum AstStructure 6 | { 7 | RecursiveList 8 | } 9 | 10 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 11 | public class AstAttribute : Attribute 12 | { 13 | public Type Type { get; set; } 14 | public string Condition { get; set; } 15 | public AstStructure Structure { get; set; } 16 | 17 | public AstAttribute(string expression) 18 | { 19 | Condition = expression; 20 | } 21 | 22 | public AstAttribute(Type type) 23 | { 24 | Type = type; 25 | } 26 | 27 | public AstAttribute(string condition, Type type) 28 | { 29 | Condition = condition; 30 | Type = type; 31 | } 32 | 33 | public AstAttribute(Type type, AstStructure structure, string expression) 34 | { 35 | Type = type; 36 | Condition = expression; 37 | Structure = structure; 38 | } 39 | 40 | public ConsumeExpression GetExpression() 41 | { 42 | return Condition != null ? ConsumeExpressionCache.Get(Condition) : null; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG.Builder 4 | { 5 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] 6 | public class ConsumeAttribute : Attribute 7 | { 8 | public string Expression { get; set; } 9 | 10 | public object Value { get; set; } 11 | 12 | public Type Type { get; set; } 13 | 14 | public Type Converter { get; set; } 15 | 16 | public object ConverterArgs { get; set; } 17 | 18 | public int Production { get; set; } 19 | 20 | public ConsumeAttribute() 21 | { 22 | } 23 | 24 | public ConsumeAttribute(string expression) 25 | : this() 26 | { 27 | Expression = expression; 28 | } 29 | 30 | public ConsumeAttribute(string expression, Type valueType) 31 | : this() 32 | { 33 | Expression = expression; 34 | Type = valueType; 35 | } 36 | 37 | public ConsumeAttribute(int production) 38 | { 39 | Production = production; 40 | } 41 | 42 | public ConsumeExpression GetExpression() 43 | { 44 | return Expression != null ? ConsumeExpressionCache.Get(Expression) : null; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeChoice.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using PEG.Cst; 4 | 5 | namespace PEG.Builder 6 | { 7 | public class ConsumeChoice : ConsumeExpression 8 | { 9 | public List Choices { get; set; } 10 | 11 | public ConsumeChoice() 12 | { 13 | Choices = new List(); 14 | } 15 | 16 | public override IEnumerable Resolve(ICstNonterminalNode node) 17 | { 18 | IEnumerable result = new ICstNode[0]; 19 | foreach (ConsumeExpression choice in Choices) 20 | { 21 | result = result.Concat(choice.Resolve(node)); 22 | } 23 | bool anyNull = result.Contains(null); 24 | return result.Cast().OrderBy(o => o.AbsoluteIndex).Cast(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeConditional.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.Cst; 3 | 4 | namespace PEG.Builder 5 | { 6 | public class ConsumeConditional : ConsumeExpression 7 | { 8 | [Consume("expression[1]")] 9 | public ConsumeExpression Predicate { get; set; } 10 | 11 | [Consume("expression[2]")] 12 | public ConsumeExpression Expression { get; set; } 13 | 14 | public override IEnumerable Resolve(ICstNonterminalNode node) 15 | { 16 | if (Predicate == null || Predicate.Resolve(node) != null) 17 | return Expression.Resolve(node); 18 | else 19 | return new ICstNode[0]; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using PEG.Cst; 4 | 5 | namespace PEG.Builder 6 | { 7 | [Consume("expression")] 8 | public abstract class ConsumeExpression 9 | { 10 | public abstract IEnumerable Resolve(ICstNonterminalNode node); 11 | 12 | public string ResolveAsString(ICstNonterminalNode node) 13 | { 14 | var result = Resolve(node); 15 | if (result.Any()) 16 | return result.First().Coalesce(); 17 | else 18 | return null; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeExpressionCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.Builder 4 | { 5 | public class ConsumeExpressionCache 6 | { 7 | private static Dictionary cache = new Dictionary(); 8 | private static object lockObject = new object(); 9 | 10 | public static ConsumeExpression Get(string expression) 11 | { 12 | ConsumeExpression result; 13 | bool found; 14 | lock (lockObject) 15 | { 16 | found = cache.TryGetValue(expression, out result); 17 | } 18 | if (!found) 19 | { 20 | result = ConsumeExpressionParsing.Parse(expression); 21 | lock (lockObject) 22 | { 23 | cache[expression] = result; 24 | } 25 | } 26 | return result; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeExpressionParsing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.Cst; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG.Builder 6 | { 7 | public class ConsumeExpressionParsing : Grammar 8 | { 9 | public virtual Expression Choice() 10 | { 11 | return Choice() + ~WS() + '|' + ~WS() + Conditional() | Conditional(); 12 | } 13 | 14 | public virtual Expression Conditional() 15 | { 16 | return Expression() | Expression() + ~WS() + ':' + ~WS() + Expression(); 17 | } 18 | 19 | public virtual Expression Expression() 20 | { 21 | return IndexExpression() | ReferenceExpression(); 22 | } 23 | 24 | public virtual Expression IndexExpression() 25 | { 26 | return Expression() + ~WS() + '[' + ~WS() + Number() + ~WS() + ']'; 27 | } 28 | 29 | public virtual Expression ReferenceExpression() 30 | { 31 | return Expression() + '.' + Identifier() | Identifier(); 32 | } 33 | 34 | public virtual Expression Identifier() 35 | { 36 | return IdentifierStartChar() + -IdentifierChar(); 37 | } 38 | 39 | public virtual Expression IdentifierChar() 40 | { 41 | return IdentifierStartChar() | Digit() | '-'; 42 | } 43 | 44 | public virtual Expression IdentifierStartChar() 45 | { 46 | return 'a'.To('z') | 'A'.To('Z'); 47 | } 48 | 49 | public virtual Expression Digit() 50 | { 51 | return '0'.To('9'); 52 | } 53 | 54 | public virtual Expression Number() 55 | { 56 | return +Digit(); 57 | } 58 | 59 | public virtual Expression WS() 60 | { 61 | return +WhitespaceChar(); 62 | } 63 | 64 | public virtual Expression WhitespaceChar() 65 | { 66 | return ' '; 67 | } 68 | 69 | public static ConsumeExpressionParsing Grammar = Create(); 70 | public static PegParser Parser = new PegParser(Grammar, Grammar.Choice()); 71 | // public static PegBuilder Builder = new PegBuilder(Grammar); 72 | 73 | public static ConsumeExpression Parse(string s) 74 | { 75 | var result = Parser.ParseString(s); 76 | 77 | // Bypass BnfBuilder because that depends on this in order to work 78 | CstNonterminalNode cst = CstBuilder.Build(result); 79 | return BuildChoice(cst); 80 | } 81 | 82 | private static ConsumeChoice BuildChoice(CstNonterminalNode node) 83 | { 84 | ConsumeChoice result = new ConsumeChoice(); 85 | CstNonterminalNode current = node; 86 | while (current != null) 87 | { 88 | if (current.Children.Count == 1) 89 | { 90 | result.Choices.Insert(0, BuildConditional((CstNonterminalNode)current.Children[0])); 91 | current = null; 92 | } 93 | else 94 | { 95 | result.Choices.Insert(0, BuildConditional((CstNonterminalNode)current.Children[2])); 96 | current = (CstNonterminalNode)current.Children[0]; 97 | } 98 | } 99 | return result; 100 | } 101 | 102 | private static ConsumeConditional BuildConditional(CstNonterminalNode node) 103 | { 104 | CstNonterminalNode predicate; 105 | CstNonterminalNode expression; 106 | if (node.Children.Count == 1) 107 | { 108 | predicate = null; 109 | expression = (CstNonterminalNode)node.Children[0]; 110 | } 111 | else 112 | { 113 | predicate = (CstNonterminalNode)node.Children[0]; 114 | expression = (CstNonterminalNode)node.Children[2]; 115 | } 116 | ConsumeConditional conditional = new ConsumeConditional(); 117 | if (predicate != null) 118 | conditional.Predicate = BuildExpression(predicate); 119 | conditional.Expression = BuildExpression(expression); 120 | return conditional; 121 | } 122 | 123 | private static ConsumeExpression BuildExpression(CstNonterminalNode node) 124 | { 125 | CstNonterminalNode expressionType = (CstNonterminalNode)node.Children[0]; 126 | if (expressionType.Nonterminal.Name == "Expression") 127 | expressionType = (CstNonterminalNode)expressionType.Children[0]; 128 | switch (expressionType.Nonterminal.Name) 129 | { 130 | case "ReferenceExpression": 131 | return BuildReferenceExpression(expressionType); 132 | case "IndexExpression": 133 | return BuildIndexExpression(expressionType); 134 | default: 135 | throw new InvalidOperationException(); 136 | } 137 | } 138 | 139 | private static ConsumeReferenceExpression BuildReferenceExpression(CstNonterminalNode node) 140 | { 141 | ConsumeReferenceExpression expression = new ConsumeReferenceExpression(); 142 | if (node.Children.Count == 1) 143 | { 144 | // CstNonterminalNode nonterminal = (CstNonterminalNode)((Token)((ICstTerminalNode)node.Children[0]).Terminal).StructuredLexeme; 145 | expression.NonTerminal = node.Children[0].Coalesce(); 146 | } 147 | else 148 | { 149 | CstNonterminalNode targetNode = (CstNonterminalNode)node.Children[0]; 150 | ConsumeExpression target = BuildExpression(targetNode); 151 | expression.Target = target; 152 | 153 | // CstNonterminalNode nonterminal = (CstNonterminalNode)((Token)((ICstTerminalNode)node.Children[2]).Terminal).StructuredLexeme; 154 | expression.NonTerminal = node.Children[2].Coalesce();//nonterminal.Coalesce(); 155 | } 156 | return expression; 157 | } 158 | 159 | private static ConsumeIndexExpression BuildIndexExpression(CstNonterminalNode node) 160 | { 161 | ConsumeIndexExpression expression = new ConsumeIndexExpression(); 162 | 163 | CstNonterminalNode targetNode = (CstNonterminalNode)node.Children[0]; 164 | expression.Target = BuildExpression(targetNode); 165 | 166 | // CstNonterminalNode indexNode = (CstNonterminalNode)((Token)((ICstTerminalNode)node.Children[2]).Terminal).StructuredLexeme; 167 | expression.Index = int.Parse(node.Children[2].Coalesce()); 168 | 169 | return expression; 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeIndexExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using PEG.Cst; 5 | using PEG.Extensions; 6 | 7 | namespace PEG.Builder 8 | { 9 | [Consume("index-expression")] 10 | public class ConsumeIndexExpression : ConsumeExpression 11 | { 12 | [Consume("expression")] 13 | public ConsumeExpression Target { get; set; } 14 | 15 | [Consume("number")] 16 | public int Index { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return Target.ToString() + '[' + Index + ']'; 21 | } 22 | 23 | public override IEnumerable Resolve(ICstNonterminalNode node) 24 | { 25 | if (Index < 1) 26 | throw new InvalidOperationException("Indices in consume attributes are 1-based: " + this); 27 | 28 | ConsumeReferenceExpression targetAsReference = (ConsumeReferenceExpression)Target; 29 | Func ret = cstNode => cstNode.Children.OfType().Where(o => o.Nonterminal.Name == targetAsReference.NonTerminal).ElementAtOrDefault(Index - 1); 30 | IEnumerable result = new ICstNode[0]; 31 | 32 | if (targetAsReference.Target != null) 33 | foreach (ICstNonterminalNode child in targetAsReference.Target.Resolve(node)) 34 | { 35 | if (child != null) 36 | { 37 | ICstNode element = ret(child); 38 | if (element != null) 39 | result = result.Union(element); 40 | } 41 | } 42 | else 43 | { 44 | ICstNode element = ret(node); 45 | if (element != null) 46 | result = result.Union(element); 47 | } 48 | 49 | return result; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /PEG/Builder/ConsumeReferenceExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PEG.Cst; 6 | 7 | namespace PEG.Builder 8 | { 9 | [Consume("reference-expression")] 10 | public class ConsumeReferenceExpression : ConsumeExpression 11 | { 12 | [Consume("expression")] 13 | public ConsumeExpression Target { get; set; } 14 | 15 | [Consume("identifier")] 16 | public string NonTerminal { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | StringBuilder result = new StringBuilder(); 21 | if (Target != null) 22 | { 23 | result.Append(Target); 24 | result.Append('.'); 25 | } 26 | result.Append(NonTerminal); 27 | return result.ToString(); 28 | } 29 | 30 | public override IEnumerable Resolve(ICstNonterminalNode node) 31 | { 32 | Func> ret = cstNode => cstNode.Children.OfType().Where(o => o.Nonterminal.Name == NonTerminal).Cast(); 33 | IEnumerable result = new ICstNode[0]; 34 | if (Target != null) 35 | { 36 | foreach (ICstNonterminalNode current in Target.Resolve(node)) 37 | { 38 | result = result.Concat(ret(current)); 39 | } 40 | } 41 | else 42 | { 43 | result = result.Concat(ret(node)); 44 | } 45 | return result; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /PEG/Cst/CstBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG.Cst 6 | { 7 | public static class CstBuilder 8 | { 9 | public static CstNonterminalNode Build(IEnumerable outputStream) 10 | { 11 | if (outputStream == null || !outputStream.Any()) 12 | return null; 13 | 14 | Stack stack = new Stack(); 15 | 16 | int absoluteIndex = 0; 17 | CstNonterminalNode current = new CstNonterminalNode(null, absoluteIndex); 18 | 19 | foreach (OutputRecord output in outputStream) 20 | { 21 | switch (output.OutputType) 22 | { 23 | case OutputType.None: 24 | current.Children.Add((Terminal)output.Expression); 25 | break; 26 | case OutputType.Begin: 27 | CstNonterminalNode nonterminalNode = new CstNonterminalNode((Nonterminal)output.Expression, ++absoluteIndex); 28 | stack.Push(current); 29 | current = nonterminalNode; 30 | break; 31 | case OutputType.End: 32 | CstNonterminalNode node = current; 33 | current = stack.Pop(); 34 | current.Children.Add(node); 35 | break; 36 | } 37 | } 38 | 39 | return current; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /PEG/Cst/CstCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG.Cst 6 | { 7 | public class CstCache 8 | { 9 | public Dictionary> cache = new Dictionary>(); 10 | 11 | public CstCache(CstNonterminalNode node) 12 | { 13 | ScanNonterminal(node); 14 | } 15 | 16 | public List this[Nonterminal nonterminal] 17 | { 18 | get 19 | { 20 | List list; 21 | cache.TryGetValue(nonterminal, out list); 22 | return list; 23 | } 24 | } 25 | 26 | private void Cache(CstNonterminalNode node) 27 | { 28 | List list; 29 | if (!cache.TryGetValue(node.Nonterminal, out list)) 30 | { 31 | list = new List(); 32 | cache[node.Nonterminal] = list; 33 | } 34 | list.Add(node); 35 | } 36 | 37 | private void ScanNonterminal(CstNonterminalNode node) 38 | { 39 | Cache(node); 40 | 41 | foreach (var child in node.Children) 42 | { 43 | if (child is CstNonterminalNode) 44 | ScanNonterminal((CstNonterminalNode)child); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /PEG/Cst/CstNonterminalNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PEG.SyntaxTree; 6 | 7 | namespace PEG.Cst 8 | { 9 | public class CstNonterminalNode : ICstNonterminalNode 10 | { 11 | public int AbsoluteIndex { get; private set; } 12 | public Nonterminal Nonterminal { get; private set; } 13 | public List Children { get; private set; } 14 | 15 | public CstNonterminalNode(Nonterminal nonterminal, int absoluteIndex) 16 | { 17 | Nonterminal = nonterminal; 18 | Children = new List(); 19 | AbsoluteIndex = absoluteIndex; 20 | } 21 | 22 | public ICstNode Transform(Func transformer) 23 | { 24 | ICstNode result = transformer(this); 25 | if (result == null) 26 | { 27 | result = new CstNonterminalNode(Nonterminal, -1); 28 | 29 | foreach (var child in Children) 30 | { 31 | if (child is CstNonterminalNode) 32 | { 33 | var nonterminalChild = (CstNonterminalNode)child; 34 | ((CstNonterminalNode)result).Children.Add(nonterminalChild.Transform(transformer)); 35 | } 36 | else 37 | ((CstNonterminalNode)result).Children.Add(child); 38 | } 39 | } 40 | return result; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return Nonterminal != null ? Nonterminal.Name : "Root"; 46 | } 47 | 48 | public string Coalesce() 49 | { 50 | StringBuilder builder = new StringBuilder(); 51 | foreach (ICstNode child in Children) 52 | { 53 | builder.Append(child.Coalesce()); 54 | } 55 | return builder.ToString(); 56 | } 57 | 58 | public IEnumerable FindAllNonterminalNodes() 59 | { 60 | Stack nodes = new Stack(); 61 | nodes.Push(this); 62 | 63 | while (nodes.Any()) 64 | { 65 | var node = nodes.Pop(); 66 | var nonterminalNode = node as ICstNonterminalNode; 67 | 68 | if (nonterminalNode != null) 69 | { 70 | yield return nonterminalNode; 71 | 72 | foreach (var child in nonterminalNode.Children) 73 | nodes.Push(child); 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /PEG/Cst/CstString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG.Cst 4 | { 5 | public class CstString : ICstNode 6 | { 7 | private string s; 8 | 9 | public CstString(string s) 10 | { 11 | this.s = s; 12 | } 13 | 14 | public string Coalesce() 15 | { 16 | return s; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /PEG/Cst/ICstNode.cs: -------------------------------------------------------------------------------- 1 | namespace PEG.Cst 2 | { 3 | public interface ICstNode 4 | { 5 | string Coalesce(); 6 | } 7 | } -------------------------------------------------------------------------------- /PEG/Cst/ICstNonterminalNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG.Cst 5 | { 6 | public interface ICstNonterminalNode : ICstNode 7 | { 8 | int AbsoluteIndex { get; } 9 | Nonterminal Nonterminal { get; } 10 | List Children { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /PEG/Cst/ICstTerminalNode.cs: -------------------------------------------------------------------------------- 1 | using PEG.SyntaxTree; 2 | 3 | namespace PEG.Cst 4 | { 5 | public interface ICstTerminalNode : ICstNode 6 | { 7 | Terminal Terminal { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /PEG/ExpressionCompiler.cs: -------------------------------------------------------------------------------- 1 | using PEG.SyntaxTree; 2 | 3 | namespace PEG 4 | { 5 | public class ExpressionCompiler : ExpressionWalker 6 | { 7 | private int nonterminalIndex; 8 | 9 | public override void Visit(Nonterminal expression, object context) 10 | { 11 | expression.Index = nonterminalIndex++; 12 | base.Visit(expression, context); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /PEG/ExpressionEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using PEG.SyntaxTree; 5 | 6 | namespace PEG 7 | { 8 | public class ExpressionEnumerator : ExpressionWalker, IEnumerable 9 | { 10 | private Expression root; 11 | private List expressions = new List(); 12 | 13 | public ExpressionEnumerator(Expression root) 14 | { 15 | this.root = root; 16 | 17 | root.Accept(this, null); 18 | } 19 | 20 | IEnumerator IEnumerable.GetEnumerator() 21 | { 22 | return GetEnumerator(); 23 | } 24 | 25 | public IEnumerator GetEnumerator() 26 | { 27 | return expressions.GetEnumerator(); 28 | } 29 | 30 | public override void Visit(AndPredicate expression, object context) 31 | { 32 | expressions.Add(expression); 33 | base.Visit(expression, context); 34 | } 35 | 36 | public override void Visit(AnyCharacter expression, object context) 37 | { 38 | expressions.Add(expression); 39 | base.Visit(expression, context); 40 | } 41 | 42 | public override void Visit(CharacterSet expression, object context) 43 | { 44 | expressions.Add(expression); 45 | base.Visit(expression, context); 46 | } 47 | 48 | public override void Visit(CharacterTerminal expression, object context) 49 | { 50 | expressions.Add(expression); 51 | base.Visit(expression, context); 52 | } 53 | 54 | public override void Visit(EmptyString expression, object context) 55 | { 56 | expressions.Add(expression); 57 | base.Visit(expression, context); 58 | } 59 | 60 | public override void Visit(Nonterminal expression, object context) 61 | { 62 | expressions.Add(expression); 63 | base.Visit(expression, context); 64 | } 65 | 66 | public override void Visit(NotPredicate expression, object context) 67 | { 68 | expressions.Add(expression); 69 | base.Visit(expression, context); 70 | } 71 | 72 | public override void Visit(OneOrMore expression, object context) 73 | { 74 | expressions.Add(expression); 75 | base.Visit(expression, context); 76 | } 77 | 78 | public override void Visit(Optional expression, object context) 79 | { 80 | expressions.Add(expression); 81 | base.Visit(expression, context); 82 | } 83 | 84 | public override void Visit(OrderedChoice expression, object context) 85 | { 86 | expressions.Add(expression); 87 | base.Visit(expression, context); 88 | } 89 | 90 | public override void Visit(Sequence expression, object context) 91 | { 92 | expressions.Add(expression); 93 | base.Visit(expression, context); 94 | } 95 | 96 | public override void Visit(Terminal expression, object context) 97 | { 98 | expressions.Add(expression); 99 | base.Visit(expression, context); 100 | } 101 | 102 | public override void Visit(Token expression, object context) 103 | { 104 | expressions.Add(expression); 105 | base.Visit(expression, context); 106 | } 107 | 108 | public override void Visit(ZeroOrMore expression, object context) 109 | { 110 | expressions.Add(expression); 111 | base.Visit(expression, context); 112 | } 113 | 114 | public override void Visit(ForeignNonterminal expression, object context) 115 | { 116 | expressions.Add(expression); 117 | base.Visit(expression, context); 118 | } 119 | 120 | public override void Visit(Substitution expression, object context) 121 | { 122 | expressions.Add(expression); 123 | base.Visit(expression, context); 124 | } 125 | 126 | public override void Visit(Repeat expression, object context) 127 | { 128 | expressions.Add(expression); 129 | base.Visit(expression, context); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /PEG/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using PEG.Cst; 4 | using PEG.Extensions; 5 | using PEG.SyntaxTree; 6 | 7 | namespace PEG 8 | { 9 | public static class ExpressionExtensions 10 | { 11 | public static CharacterSet To(this char from, char to) 12 | { 13 | if (from == '(' && to == ')') 14 | { 15 | char[] symbols = 16 | { 17 | '-', '\'', '\\', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '[', 18 | ']', '{', '}', '|', ';', ':', '\"', '<', '>', ',', '.', '/', '?' 19 | }; 20 | return new CharacterRange(from, to, symbols.Select(x => (CharacterTerminal)x)); 21 | } 22 | else if ((from == 'A' && to == 'z') || (from == 'a' && to == 'Z')) 23 | { 24 | return new CharacterRange(from, to, from.RangeTo(to).Select(x => (CharacterTerminal)x)); 25 | } 26 | else 27 | { 28 | return new CharacterRange(from, to, from.RangeTo(to).Select(x => (CharacterTerminal)x).ToArray()); 29 | } 30 | } 31 | 32 | /// 33 | /// Returns true if the pattern matches the specified input. This requires an exact 34 | /// match, contrasted against Contains(...). 35 | /// 36 | public static bool Match(this Expression expression, string input) 37 | { 38 | var parser = CreateParser(expression); 39 | var result = parser.ParseString(input); 40 | return result != null && result.Any(); 41 | } 42 | 43 | /// 44 | /// Returns true if the pattern is contained by the specified input. This performs 45 | /// a partial match. If the pattern is found anywhere in the specifieed input, 46 | /// this returns true. 47 | /// 48 | public static bool Contains(this Expression expression, string input) 49 | { 50 | var exp = +(expression | new AnyCharacter(false)); 51 | var parser = CreateParser(exp); 52 | IEnumerable outputRecords = parser.ParseString(input); 53 | return outputRecords.Skip(2).Any(); // Skip begin/end of implicit root nonterminal 54 | } 55 | 56 | /// 57 | /// PEG requires that the nonterminals be indexed in so that they may be accessed 58 | /// quickly from an array. Compiling the expression will ensure all nonterminals 59 | /// contained in the expression (and its descendents) are properly indexed. 60 | /// 61 | /// 62 | /// 63 | private static void Compile(this Expression expression) 64 | { 65 | var compiler = new ExpressionCompiler(); 66 | expression.Accept(compiler, null); 67 | } 68 | 69 | /// 70 | /// Performs a transformation on the input substituting the values as defined by the provided 71 | /// replacements. This requires an exact match similar to the Match(...) method. 72 | /// 73 | public static string Transform(this Expression expression, string input, params Replacement[] replacements) 74 | { 75 | var parser = CreateParser(expression); 76 | 77 | var output = parser.ParseString(input); 78 | if (!output.Any()) 79 | return input; 80 | 81 | var cst = CstBuilder.Build(output); 82 | cst = (CstNonterminalNode)cst.Children[0]; 83 | var cache = new CstCache(cst); 84 | var map = replacements.ToDictionary(x => x.From, x => x.To.Replace(cache)); 85 | var transformed = cst.Transform(x => map.ContainsKey(x.Nonterminal) ? map[x.Nonterminal] : null); 86 | 87 | return transformed.Coalesce(); 88 | } 89 | 90 | /// 91 | /// Replaces all occurances of the pattern within input according to the definition provided 92 | /// by replacements. 93 | /// 94 | public static string Replace(this Expression expression, string input, params Replacement[] replacements) 95 | { 96 | // We want the expression to act like a nonterminal so that its contents get grouped up in the CST 97 | if (!(expression is Nonterminal)) 98 | expression = expression.Capture(); 99 | 100 | // Modify the expression to allow any character (allows matching substrings) 101 | var exp = +(expression | new AnyCharacter(true)); 102 | 103 | var parser = CreateParser(exp); 104 | 105 | var output = parser.ParseString(input); 106 | if (!output.Any()) 107 | return input; 108 | 109 | var cst = CstBuilder.Build(output); 110 | cst = (CstNonterminalNode)cst.Children[0]; 111 | 112 | var transformedCst = new CstNonterminalNode(cst.Nonterminal, -1); 113 | foreach (var child in cst.Children) 114 | { 115 | if (child is CstNonterminalNode) 116 | { 117 | var childNonterminal = (CstNonterminalNode)child; 118 | var cache = new CstCache(childNonterminal); 119 | var map = replacements.ToDictionary(x => x.From, x => x.To.Replace(cache)); 120 | var transformed = childNonterminal.Transform(x => map.ContainsKey(x.Nonterminal) ? map[x.Nonterminal] : null); 121 | transformedCst.Children.Add(transformed); 122 | } 123 | else 124 | { 125 | transformedCst.Children.Add(child); 126 | } 127 | } 128 | 129 | return transformedCst.Coalesce(); 130 | } 131 | 132 | public static PegParser CreateParser(Expression expression) 133 | { 134 | Grammar grammar = new Grammar(); 135 | var nonterminal = expression is Nonterminal ? (Nonterminal)expression : new Nonterminal(expression); 136 | grammar.Nonterminals.AddRange(nonterminal.Enumerate().OfType()); 137 | 138 | // Compile is necessary to ensure nonterminals have valid indices 139 | nonterminal.Compile(); 140 | 141 | PegParser parser = new PegParser(grammar, nonterminal); 142 | return parser; 143 | } 144 | 145 | public static Dictionary Parse(this Expression expression, string input) 146 | { 147 | var parser = CreateParser(expression); 148 | var output = CstBuilder.Build(parser.ParseString(input)); 149 | if (output == null) 150 | return null; 151 | 152 | var dictionary = new Dictionary(); 153 | foreach (var nonterminal in output.FindAllNonterminalNodes()) 154 | if (nonterminal.Nonterminal != null) 155 | dictionary[nonterminal.Nonterminal] = nonterminal.Coalesce(); 156 | 157 | return dictionary; 158 | } 159 | 160 | public static IEnumerable Enumerate(this Expression expression) 161 | { 162 | return new ExpressionEnumerator(expression); 163 | } 164 | 165 | /// 166 | /// Shortcut for character terminals. 'a'._() converts 'a' into the CharacterTerminal 167 | /// expression representing the character 'a'. This is a common operation and this seems 168 | /// like the most concise of representing it. 169 | /// 170 | public static CharacterTerminal _(this char c) 171 | { 172 | return new CharacterTerminal(c); 173 | } 174 | 175 | public static Sequence _(this string s) 176 | { 177 | return s; 178 | } 179 | 180 | /// 181 | /// Returns a Nonterminal that references the current expression, giving it the specified 182 | /// name. Nonterminals are the only structure that retains structural meaning after 183 | /// parsing (i.e. it has representation in the output stream / CST.) Thus, if you ever 184 | /// need to do something to your parse result by referring to elements in your pattern, 185 | /// this is how you can solidify them. 186 | /// 187 | public static Nonterminal Capture(this Expression expression, string name) 188 | { 189 | Nonterminal result = new Nonterminal(); 190 | result.Name = name; 191 | result.Expression = expression; 192 | result.Index = -1; 193 | return result; 194 | } 195 | 196 | /// 197 | /// Returns a Nonterminal that references the current expression. Nonterminals are 198 | /// the only structure that retains structural meaning after parsing (i.e. it 199 | /// has representation in the output stream / CST.) Thus, if you ever need to do 200 | /// something to your parse result by referring to elements in your pattern, 201 | /// this is how you can solidify them. 202 | /// 203 | public static Nonterminal Capture(this Expression expression) 204 | { 205 | Nonterminal result = new Nonterminal(); 206 | result.Expression = expression; 207 | result.Index = -1; 208 | return result; 209 | } 210 | 211 | public static Replacement To(this Nonterminal from, Nonterminal to) 212 | { 213 | return new Replacement { From = from, To = new NonterminalReplacementTarget(to) }; 214 | } 215 | 216 | public static Replacement To(this Nonterminal from, string to) 217 | { 218 | return new Replacement { From = from, To = new StringReplacementTarget(to) }; 219 | } 220 | 221 | public static Expression Repeat(this Expression expression, int count) 222 | { 223 | Repeat repeat = new Repeat(); 224 | repeat.Operand = expression; 225 | repeat.Min = count; 226 | repeat.Max = count; 227 | return repeat; 228 | } 229 | 230 | public static Expression Repeat(this Expression expression, int min, int max) 231 | { 232 | Repeat repeat = new Repeat(); 233 | repeat.Operand = expression; 234 | repeat.Min = min; 235 | repeat.Max = max; 236 | return repeat; 237 | } 238 | 239 | public static AndPredicate And(this Expression operand) 240 | { 241 | AndPredicate andPredicate = new AndPredicate(); 242 | andPredicate.Operand = operand; 243 | return andPredicate; 244 | } 245 | 246 | public static EncloseExpression Enclose(this char enclosure, Expression operand) 247 | { 248 | return enclosure._().Enclose(operand); 249 | } 250 | 251 | public static EncloseExpression Enclose(this string enclosure, Expression operand) 252 | { 253 | return enclosure._().Enclose(operand); 254 | } 255 | 256 | public static EncloseExpression Enclose(this Expression enclosure, Expression operand) 257 | { 258 | EncloseExpression enclose = new EncloseExpression(); 259 | enclose.Enclosure = enclosure; 260 | enclose.Operand = operand; 261 | return enclose; 262 | } 263 | 264 | public static AnyCharacter Any(this char c) 265 | { 266 | return new AnyCharacter(); 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /PEG/ExpressionWalker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG 5 | { 6 | public class ExpressionWalker : IExpressionVisitor 7 | { 8 | public virtual void Visit(AndPredicate expression, T context) 9 | { 10 | expression.Operand.Accept(this, context); 11 | } 12 | 13 | public virtual void Visit(AnyCharacter expression, T context) 14 | { 15 | } 16 | 17 | public virtual void Visit(CharacterSet expression, T context) 18 | { 19 | foreach (var c in expression.Characters) 20 | c.Accept(this, context); 21 | } 22 | 23 | public virtual void Visit(CharacterTerminal expression, T context) 24 | { 25 | } 26 | 27 | public virtual void Visit(EmptyString expression, T context) 28 | { 29 | } 30 | 31 | public virtual void Visit(Nonterminal expression, T context) 32 | { 33 | expression.Expression.Accept(this, context); 34 | } 35 | 36 | public virtual void Visit(NotPredicate expression, T context) 37 | { 38 | expression.Operand.Accept(this, context); 39 | } 40 | 41 | public virtual void Visit(OneOrMore expression, T context) 42 | { 43 | expression.Operand.Accept(this, context); 44 | } 45 | 46 | public virtual void Visit(Optional expression, T context) 47 | { 48 | expression.Operand.Accept(this, context); 49 | } 50 | 51 | public virtual void Visit(OrderedChoice expression, T context) 52 | { 53 | foreach (var current in expression.Expressions) 54 | current.Accept(this, context); 55 | } 56 | 57 | public virtual void Visit(Sequence expression, T context) 58 | { 59 | foreach (var current in expression.Expressions) 60 | current.Accept(this, context); 61 | } 62 | 63 | public virtual void Visit(Terminal expression, T context) 64 | { 65 | } 66 | 67 | public virtual void Visit(Token expression, T context) 68 | { 69 | } 70 | 71 | public virtual void Visit(ZeroOrMore expression, T context) 72 | { 73 | expression.Operand.Accept(this, context); 74 | } 75 | 76 | public virtual void Visit(ForeignNonterminal expression, T context) 77 | { 78 | } 79 | 80 | public virtual void Visit(Substitution expression, T context) 81 | { 82 | } 83 | 84 | public virtual void Visit(Repeat expression, T context) 85 | { 86 | expression.Operand.Accept(this, context); 87 | } 88 | 89 | public void Visit(EncloseExpression expression, T context) 90 | { 91 | expression.Enclosure.Accept(this, context); 92 | expression.Operand.Accept(this, context); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /PEG/Extensions/AttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace PEG.Extensions 6 | { 7 | public static class AttributeExtensions 8 | { 9 | public static T GetAttribute(this MemberInfo type) where T : Attribute 10 | { 11 | return type.GetAttributes().FirstOrDefault(); 12 | } 13 | 14 | public static T[] GetAttributes(this MemberInfo type) where T : Attribute 15 | { 16 | return Attribute.GetCustomAttributes(type, typeof(T), true).Cast().ToArray(); 17 | } 18 | 19 | public static bool HasAttribute(this MemberInfo type) where T : Attribute 20 | { 21 | return Attribute.IsDefined(type, typeof(T), true); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /PEG/Extensions/CharExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PEG.Extensions 5 | { 6 | public static class CharExtensions 7 | { 8 | public static IEnumerable RangeTo(this char startCharacter, char endCharacter) 9 | { 10 | if (startCharacter == '(' || endCharacter == ')') 11 | { 12 | foreach (char c in new[] { '-', '\'', '\\', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '[', ']', '{', '}', '|', ';', ':', '\"', '<', '>', ',', '.', '/', '?' }) 13 | yield return c; 14 | } 15 | else if ((startCharacter == 'A' && endCharacter == 'z') || (startCharacter == 'a' && endCharacter == 'Z')) 16 | { 17 | foreach (char c in 'A'.RangeTo('Z').Concat('a'.RangeTo('z'))) 18 | yield return c; 19 | } 20 | else 21 | { 22 | for (char c = startCharacter; c <= endCharacter; c++) 23 | yield return c; 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PEG.Extensions 5 | { 6 | public static class EnumerableExtensions 7 | { 8 | public static IEnumerable Union(this IEnumerable enumerable, T element) 9 | { 10 | foreach (T t in enumerable) 11 | yield return t; 12 | yield return element; 13 | } 14 | 15 | public static void Foreach(this IEnumerable list, Action action) 16 | { 17 | foreach (T o in list) 18 | action(o); 19 | } 20 | 21 | public static void Foreach(this IEnumerable list, Func action) 22 | { 23 | foreach (T o in list) 24 | action(o); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG/Extensions/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Reflection; 5 | 6 | namespace PEG.Extensions 7 | { 8 | public static class ReflectionExtensions 9 | { 10 | public static IEnumerable GetAllPropertiesInAncestry(this Type type) 11 | { 12 | Type current = type; 13 | while (current != null) 14 | { 15 | foreach (var property in current.GetProperties()) 16 | yield return property; 17 | current = current.BaseType; 18 | } 19 | foreach (Type intf in type.GetInterfaces()) 20 | { 21 | foreach (var property in intf.GetProperties()) 22 | yield return property; 23 | } 24 | } 25 | 26 | public static bool IsGenericList(this Type listType) 27 | { 28 | return IsType(listType, typeof(List<>)) || IsType(listType, typeof(Collection<>)) || IsType(listType, typeof(IList<>)); 29 | } 30 | 31 | public static bool IsType(Type type, Type ancestor) 32 | { 33 | while (type != null) 34 | { 35 | if (type.IsGenericType) 36 | type = type.GetGenericTypeDefinition(); 37 | if (type == ancestor) 38 | return true; 39 | type = type.BaseType; 40 | } 41 | return false; 42 | } 43 | 44 | public static Type GetListElementType(this Type listType) 45 | { 46 | return listType.GetGenericArgument(typeof(IList<>), 0); 47 | } 48 | 49 | public static IEnumerable GetComposition(this Type type) 50 | { 51 | return type.GetAncestry(true); 52 | } 53 | 54 | public static IEnumerable GetAncestry(this Type type) 55 | { 56 | return type.GetAncestry(false); 57 | } 58 | 59 | public static string GetPath(this PropertyInfo property) 60 | { 61 | return property.DeclaringType.FullName + '.' + property.Name; 62 | } 63 | 64 | public static EnumRecord[] GetEnums(this Type type) 65 | { 66 | string[] names = Enum.GetNames(type); 67 | EnumRecord[] result = new EnumRecord[names.Length]; 68 | for (int i = 0; i < names.Length; i++) 69 | { 70 | string name = names[i]; 71 | FieldInfo field = type.GetField(name); 72 | EnumRecord record = new EnumRecord(field); 73 | result[i] = record; 74 | } 75 | return result; 76 | } 77 | 78 | public struct EnumRecord 79 | { 80 | public FieldInfo Field { get; set; } 81 | 82 | public EnumRecord(FieldInfo field) 83 | : this() 84 | { 85 | Field = field; 86 | } 87 | 88 | public string Name 89 | { 90 | get { return Field.Name; } 91 | } 92 | 93 | public object Value 94 | { 95 | get { return Field.GetValue(null); } 96 | } 97 | } 98 | 99 | private static IEnumerable GetAncestry(this Type type, bool includeInterfaces) 100 | { 101 | Type current = type; 102 | while (current != null) 103 | { 104 | yield return current; 105 | current = current.BaseType; 106 | } 107 | if (includeInterfaces) 108 | { 109 | Type[] interfaces = type.GetInterfaces(); 110 | foreach (Type @interface in interfaces) 111 | { 112 | yield return @interface; 113 | } 114 | } 115 | } 116 | 117 | public static Type GetGenericArgument(this Type type, Type typeToGetParameterFrom, int argumentIndex) 118 | { 119 | foreach (Type current in type.GetComposition()) 120 | { 121 | type = current; 122 | if (!current.IsGenericType) 123 | continue; 124 | 125 | Type genericTypeDefinition = current.GetGenericTypeDefinition(); 126 | if (genericTypeDefinition == typeToGetParameterFrom) 127 | break; 128 | } 129 | 130 | Type[] genericArgs = type.GetGenericArguments(); 131 | 132 | Type result = genericArgs[argumentIndex]; 133 | return result; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /PEG/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PEG.Extensions 6 | { 7 | public static class StringExtensions 8 | { 9 | public static string GetLine(this string s, int offset, out int lineOffset) 10 | { 11 | if (offset >= s.Length) 12 | { 13 | string result = GetLine(s, s.Length - 1, out lineOffset); 14 | lineOffset = result.Length + (offset - s.Length) + 1; 15 | return result; 16 | } 17 | 18 | int start = s.LastIndexOfAny(new[] { '\n', '\r' }, offset); 19 | if (start == -1) 20 | start = 0; 21 | else if (start < offset) // If start == offset, then the offset is at a line break. The code below will adjust for that and get the previous line 22 | start++; 23 | 24 | int end = s.IndexOfAny(new[] { '\n', '\r' }, offset); 25 | if (end == -1) 26 | end = s.Length; 27 | 28 | if (start == end && offset > 0) 29 | { 30 | string result = GetLine(s, offset - 1, out lineOffset); 31 | lineOffset = result.Length + 1; 32 | return result; 33 | } 34 | 35 | lineOffset = offset - start; 36 | return s.Substring(start, end - start); 37 | } 38 | 39 | public static string Delimit(this IEnumerable list, string delimiter) 40 | { 41 | if (list == null) 42 | return ""; 43 | return Delimit(list, delimiter, o => o != null ? o.ToString() : ""); 44 | } 45 | 46 | public static string Delimit(this IEnumerable list, string delimiter, Func toString) 47 | { 48 | StringBuilder result = new StringBuilder(); 49 | Delimit(list, t => result.Append(toString(t)), () => result.Append(delimiter)); 50 | return result.ToString(); 51 | } 52 | 53 | public static void Delimit(this IEnumerable list, Action action, Action delimiterAction) 54 | { 55 | bool first = true; 56 | foreach (T o in list) 57 | { 58 | if (!first) 59 | delimiterAction(); 60 | action(o); 61 | first = false; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /PEG/FirstSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.SyntaxTree; 3 | using PEG.Utils; 4 | 5 | namespace PEG 6 | { 7 | /// 8 | /// NOT FUNCTIONAL YET 9 | /// Future plan to implement first sets in the PEG parser for choice operation optimizations. 10 | /// 11 | public class FirstSet : IExpressionVisitor 12 | { 13 | public DictionarySet nonterminalSet = new DictionarySet(); 14 | public DictionarySet expressionSet = new DictionarySet(); 15 | 16 | private int totalCount; 17 | 18 | public FirstSet(Grammar grammar) 19 | { 20 | int lastCount; 21 | do 22 | { 23 | lastCount = totalCount; 24 | foreach (Nonterminal nonterminal in grammar.Nonterminals) 25 | { 26 | nonterminal.Expression.Accept(this, nonterminal); 27 | } 28 | } 29 | while (lastCount != totalCount); 30 | } 31 | 32 | private void Add(Nonterminal key, Expression expression, Terminal value) 33 | { 34 | nonterminalSet[key].Add(value); 35 | if (expressionSet[expression].Add(value)) 36 | totalCount++; 37 | } 38 | 39 | public bool Contains(Nonterminal expression, Terminal terminal) 40 | { 41 | return nonterminalSet[expression].Contains(terminal); 42 | } 43 | 44 | public void Visit(AndPredicate expression, Nonterminal context) 45 | { 46 | expression.Operand.Accept(this, context); 47 | } 48 | 49 | public void Visit(AnyCharacter expression, Nonterminal context) 50 | { 51 | Add(context, expression, AnyCharacterTerminal.Instance); 52 | } 53 | 54 | public void Visit(CharacterSet expression, Nonterminal context) 55 | { 56 | foreach (var character in expression.Characters) 57 | Add(context, expression, character); 58 | } 59 | 60 | public void Visit(CharacterTerminal expression, Nonterminal context) 61 | { 62 | Add(context, expression, expression); 63 | } 64 | 65 | public void Visit(EmptyString expression, Nonterminal context) 66 | { 67 | Add(context, expression, NullTerminal.Instance); 68 | } 69 | 70 | public void Visit(Nonterminal expression, Nonterminal context) 71 | { 72 | // Intentionally do not want to traverse nonterminals since that is handled in the primary loop 73 | // (prevent infinite recursion) 74 | } 75 | 76 | public void Visit(NotPredicate expression, Nonterminal context) 77 | { 78 | expression.Operand.Accept(this, context); 79 | } 80 | 81 | public void Visit(OneOrMore expression, Nonterminal context) 82 | { 83 | expression.Operand.Accept(this, context); 84 | } 85 | 86 | public void Visit(Optional expression, Nonterminal context) 87 | { 88 | Add(context, expression, NullTerminal.Instance); 89 | expression.Operand.Accept(this, context); 90 | } 91 | 92 | public void Visit(OrderedChoice expression, Nonterminal context) 93 | { 94 | foreach (var item in expression.Expressions) 95 | item.Accept(this, context); 96 | } 97 | 98 | public void Visit(Sequence expression, Nonterminal context) 99 | { 100 | foreach (var item in expression.Expressions) 101 | { 102 | item.Accept(this, context); 103 | if (!expressionSet[item].Contains(NullTerminal.Instance)) 104 | break; 105 | } 106 | } 107 | 108 | public void Visit(Terminal expression, Nonterminal context) 109 | { 110 | Add(context, expression, expression); 111 | } 112 | 113 | public void Visit(Token expression, Nonterminal context) 114 | { 115 | Add(context, expression, expression); 116 | } 117 | 118 | public void Visit(ZeroOrMore expression, Nonterminal context) 119 | { 120 | expression.Operand.Accept(this, context); 121 | } 122 | 123 | public void Visit(ForeignNonterminal expression, Nonterminal context) 124 | { 125 | } 126 | 127 | public void Visit(Substitution expression, Nonterminal context) 128 | { 129 | } 130 | 131 | public void Visit(Repeat expression, Nonterminal context) 132 | { 133 | } 134 | 135 | public void Visit(EncloseExpression expression, Nonterminal context) 136 | { 137 | throw new NotImplementedException(); 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /PEG/Grammar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using PEG.SyntaxTree; 6 | using Expression = PEG.SyntaxTree.Expression; 7 | 8 | namespace PEG 9 | { 10 | public class Grammar 11 | { 12 | public static EmptyString E = new EmptyString(); 13 | public static AnyCharacter Any = new AnyCharacter(); 14 | 15 | public List Nonterminals { get; set; } 16 | public Expression StartExpression { get; set; } 17 | public Grammar TokenizerGrammar { get; private set; } 18 | public Nonterminal Tokenizer { get; set; } 19 | public bool IsTokenGrammar { get; private set; } 20 | 21 | // public FirstSet FirstSet { get; set; } 22 | 23 | public Grammar() 24 | { 25 | Nonterminals = new List(); 26 | } 27 | 28 | public void SetTokenizer(Grammar tokenizerGrammar, Nonterminal tokenizer) 29 | { 30 | TokenizerGrammar = tokenizerGrammar; 31 | Tokenizer = tokenizer; 32 | tokenizerGrammar.IsTokenGrammar = true; 33 | } 34 | 35 | public Nonterminal GetNonterminal(string name) 36 | { 37 | return Nonterminals.Where(o => o.Name == name).FirstOrDefault(); 38 | } 39 | } 40 | 41 | public class Grammar : Grammar where T : Grammar 42 | { 43 | public static T Create(params object[] args) 44 | { 45 | Grammar grammar = GrammarFactory.Create(args); 46 | grammar.Initialize(); 47 | return (T)grammar; 48 | } 49 | 50 | internal void NotifyCreated(object[] args) 51 | { 52 | OnCreated(args); 53 | } 54 | 55 | protected virtual void OnCreated(object[] args) 56 | { 57 | } 58 | 59 | public void Initialize() 60 | { 61 | var missingVirtual = typeof(T).GetMethods().Where(x => x.DeclaringType != typeof(Expression) && x.DeclaringType != typeof(Grammar) && x.ReturnType == typeof(Expression) && !x.IsVirtual); 62 | if (missingVirtual.Any()) 63 | throw new InvalidOperationException("The expression methods in your grammar must be declared virtual: " + string.Join(", ", missingVirtual.Select(x => x.Name))); 64 | } 65 | 66 | public Nonterminal GetNonterminal(Expression> nonterminal) 67 | { 68 | MethodCallExpression method = (MethodCallExpression)nonterminal.Body; 69 | return GetNonterminal(method.Method.Name); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /PEG/GrammarFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using PEG.Extensions; 5 | using PEG.Proxies; 6 | using PEG.SyntaxTree; 7 | 8 | namespace PEG 9 | { 10 | public class GrammarFactory where T : Grammar 11 | { 12 | public static T Create(object[] args) 13 | { 14 | GrammarFactory factory = new GrammarFactory(); 15 | T result = Proxy.CreateProxy(factory.InvocationHandler); 16 | factory.grammar = result; 17 | result.NotifyCreated(args); 18 | 19 | // Now initialize the grammar 20 | foreach (MethodInfo method in typeof(T).GetMethods()) 21 | { 22 | if (method.ReturnType == typeof(Expression) && method.GetParameters().Length == 0 && !method.IsSpecialName) 23 | { 24 | Nonterminal rule = new Nonterminal(); 25 | rule.Name = method.Name; 26 | rule.Index = factory.nonterminalIndex++; 27 | result.Nonterminals.Add(rule); 28 | 29 | // if (method.HasAttribute()) 30 | // result.StartRule = rule; 31 | 32 | factory.rules[method.Name] = rule; 33 | } 34 | } 35 | foreach (MethodInfo method in typeof(T).GetMethods()) 36 | { 37 | if (method.ReturnType == typeof(Expression) && method.GetParameters().Length == 0 && !method.IsSpecialName) 38 | { 39 | method.Invoke(result, null); 40 | } 41 | } 42 | foreach (MethodInfo method in typeof(T).GetMethods()) 43 | { 44 | if (method.ReturnType == typeof(Expression) && method.GetParameters().Length == 0 && !method.IsSpecialName) 45 | { 46 | Nonterminal rule = factory.rules[method.Name]; 47 | if (method.HasAttribute()) 48 | { 49 | result.Tokenizer = rule; 50 | } 51 | TokenAttribute tokenAttribute = method.GetAttribute(); 52 | if (tokenAttribute != null) 53 | { 54 | if (!tokenAttribute.TokenizeChildren) 55 | { 56 | rule.IsToken = true; 57 | if (tokenAttribute.IsOmitted) 58 | rule.IsTokenOmitted = true; 59 | } 60 | else 61 | { 62 | OrderedChoice children = (OrderedChoice)rule.Expression; 63 | foreach (var child in children.Expressions) 64 | { 65 | Nonterminal childRule = (Nonterminal)child; 66 | childRule.IsToken = true; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | FieldInfo tokenizerGrammarField = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(o => typeof(Grammar).IsAssignableFrom(o.FieldType) && o.HasAttribute()).FirstOrDefault(); 74 | if (tokenizerGrammarField != null) 75 | { 76 | Grammar tokenizerGrammar = (Grammar)tokenizerGrammarField.GetValue(result); 77 | if (tokenizerGrammar.Tokenizer != null) 78 | result.SetTokenizer(tokenizerGrammar, tokenizerGrammar.Tokenizer); 79 | } 80 | 81 | return result; 82 | } 83 | 84 | private T grammar; 85 | private MethodInfo activeMethod; 86 | private Dictionary rules = new Dictionary(); 87 | private int nonterminalIndex; 88 | 89 | /// 90 | /// The point of this is to evaluate all of the expressions and assign them to the rule (NonTerminal). The 91 | /// "activeMethod" business is to escape recursion if a rule calls itself. 92 | /// 93 | private void InvocationHandler(Invocation invocation) 94 | { 95 | Nonterminal result; 96 | if (rules.TryGetValue(invocation.Method.Name, out result) && result.Expression != null) 97 | invocation.ReturnValue = result; 98 | else 99 | { 100 | if (activeMethod != null) 101 | { 102 | Nonterminal rule = rules[invocation.Method.Name]; 103 | invocation.ReturnValue = rule; 104 | } 105 | else 106 | { 107 | activeMethod = invocation.Method; 108 | invocation.Proceed(); 109 | Nonterminal rule = rules[invocation.Method.Name]; 110 | if (rule.Expression == null) 111 | rule.Expression = (Expression)invocation.ReturnValue; 112 | invocation.ReturnValue = rule; 113 | 114 | // Now check for captured nonterminals (i.e. .Capture("foo")) 115 | var walker = new CaptureWalker(); 116 | rule.Expression.Accept(walker, this); 117 | 118 | activeMethod = null; 119 | } 120 | } 121 | } 122 | 123 | /// 124 | /// Looks for captured nonterminals so we can give them an index and register them. 125 | /// 126 | private class CaptureWalker : ExpressionWalker> 127 | { 128 | public override void Visit(Nonterminal expression, GrammarFactory context) 129 | { 130 | // Captured nonterminals are given an index of -1 when created. 131 | if (expression.Index == -1) 132 | { 133 | expression.Index = context.nonterminalIndex++; 134 | context.grammar.Nonterminals.Add(expression); 135 | 136 | // For these we still want to propagate the walk since there could be more nested content 137 | base.Visit(expression, context); 138 | } 139 | else 140 | { 141 | // We don't propagate the walk since we don't want to follow into any other nonterminals 142 | } 143 | } 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /PEG/IParseInput.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG 5 | { 6 | public interface IParseInput : IEnumerable 7 | { 8 | Terminal this[int index] { get; } 9 | int Length { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /PEG/IPegBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace PEG 2 | { 3 | public interface IPegBuilder 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /PEG/IReplacementTarget.cs: -------------------------------------------------------------------------------- 1 | using PEG.Cst; 2 | 3 | namespace PEG 4 | { 5 | public interface IReplacementTarget 6 | { 7 | ICstNode Replace(CstCache cache); 8 | } 9 | } -------------------------------------------------------------------------------- /PEG/LeftRecursion.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.SyntaxTree; 3 | using PEG.Utils; 4 | 5 | namespace PEG 6 | { 7 | public class LeftRecursion 8 | { 9 | public Nonterminal Rule { get; set; } 10 | public BooleanSet InvolvedSet { get; set; } 11 | public BooleanSet EvalSet { get; set; } 12 | public LeftRecursion Next { get; set; } 13 | 14 | public LeftRecursion(Nonterminal rule, Grammar grammar) 15 | { 16 | Rule = rule; 17 | int size = grammar.Nonterminals.Count; 18 | InvolvedSet = new BooleanSet(size); 19 | EvalSet = new BooleanSet(size); 20 | } 21 | 22 | public LeftRecursion(Nonterminal rule, LeftRecursion next, Grammar grammar) 23 | { 24 | Rule = rule; 25 | InvolvedSet = new BooleanSet(grammar.Nonterminals.Count); 26 | Next = next; 27 | EvalSet = next.EvalSet.Copy(); 28 | EvalSet.Add(next.Rule.Index); 29 | } 30 | 31 | public void Add(IEnumerable involvedSet) 32 | { 33 | foreach (Nonterminal nonterminal in involvedSet) 34 | { 35 | InvolvedSet.Add(nonterminal.Index); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /PEG/LrParseEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using PEG.SyntaxTree; 4 | using PEG.Utils; 5 | 6 | namespace PEG 7 | { 8 | /// 9 | /// Enhances the default PEG parsing engine to support left-recursion. 10 | /// 11 | public class LrParseEngine : ParseEngine 12 | { 13 | /// 14 | /// Each entry corresponds to the index into the input. Each time left-recursion is instigated, 15 | /// A LeftRecursion entry is placed into this array. If one is already present, a new one will 16 | /// be "pushed" into the array, keeping a reference to the old one -- this one will be restored 17 | /// once the new left-recursion is finished. 18 | /// 19 | public LeftRecursion[] LeftRecursionSet { get; set; } 20 | 21 | /// 22 | /// Each entry corresponds to the index into the input. Within the BooleanSet, each entry 23 | /// maps the index of a nonterminal with a boolean state indicating whether the nonterminal 24 | /// is currently being invoked. 25 | /// 26 | public BooleanSet[] InvocationSet { get; set; } 27 | 28 | /// 29 | /// Reproduces the nonterminal callstack so we can accurately back-calculate the EvalSet when 30 | /// left-recursion "happens". 31 | /// 32 | public Stack CallStack { get; set; } 33 | 34 | public LrParseEngine(Grammar grammar, string input) : base(grammar, input) 35 | { 36 | Init(); 37 | } 38 | 39 | public LrParseEngine(Grammar grammar, string rawInput, IParseInput input) : base(grammar, rawInput, input) 40 | { 41 | Init(); 42 | } 43 | 44 | private void Init() 45 | { 46 | LeftRecursionSet = new LeftRecursion[Input.Length + 1]; 47 | InvocationSet = new BooleanSet[Input.Length + 1]; 48 | for (int i = 0; i < InvocationSet.Length; i++) 49 | InvocationSet[i] = new BooleanSet(Grammar.Nonterminals.Count); 50 | CallStack = new Stack(); 51 | } 52 | 53 | private IEnumerable LeftRecursion = new List(); 54 | 55 | public override IEnumerable ApplyNonterminal(Nonterminal nonterminal, int position) 56 | { 57 | Depth++; 58 | 59 | try 60 | { 61 | MemoEntry memoEntry = MemoTable[position, nonterminal.Index]; 62 | LeftRecursion leftRecursion = LeftRecursionSet[position]; 63 | if (memoEntry == null || (leftRecursion != null && leftRecursion.InvolvedSet.Contains(nonterminal.Index))) 64 | { 65 | // If we've detected left recursion... 66 | if (InvocationSet[position][nonterminal.Index]) 67 | { 68 | // If there is no left recursion already going on... 69 | if (leftRecursion == null) 70 | { 71 | // Create a new left recursion entry and store it in the LeftRecursionSet for the current position. 72 | leftRecursion = new LeftRecursion(nonterminal, Grammar); 73 | LeftRecursionSet[position] = leftRecursion; 74 | } 75 | 76 | // If there already is left-recursion going on, we may need to start keeping track of a new thread. Fortunately, 77 | // we can know that the new left-recursive state will end before we have to worry about the old one again, so 78 | // we can create a linked-list-based stack where each "Next" entry is further up the call stack. The inverse 79 | // of this process is near the end. 80 | else if (leftRecursion.Rule != nonterminal) 81 | { 82 | // This is here because we don't ever want to add an entry to the stack that is already being evaluated. 83 | if (leftRecursion.EvalSet.Contains(nonterminal.Index)) 84 | return LeftRecursion; 85 | 86 | var nextLeftRecursion = leftRecursion; 87 | leftRecursion = new LeftRecursion(nonterminal, nextLeftRecursion, Grammar); 88 | } 89 | 90 | // This ensures that we keep track of those nonterminals that were involved in the left recursion. (i.e. 91 | // all the nonterminals in the call stack following the nonterminal responsible for the left-recursion. 92 | // This is non-inclusive -- it does not include the offending nonterminal located on both sides.) All such 93 | // nonterminals are added to the InvolvedSet of the LeftRecursion record. 94 | leftRecursion.Add(CallStack.TakeWhile(o => o != nonterminal)); 95 | 96 | // Keep track of this LeftRecursion at the current position (possibly overwriting a previous entry -- this 97 | // entry will be restored when the current left recursion finishes) 98 | LeftRecursionSet[position] = leftRecursion; 99 | 100 | // Return the special LeftRecursion value that indicates a (special) failed attempt. Below, we check for 101 | // this value to ensure that the memo table is not updated with the failure when left-recursion is still 102 | // going on (i.e. we will need to make another attempt later and don't want to have it return a failure 103 | // because of the memo result) 104 | return LeftRecursion; 105 | } 106 | else 107 | { 108 | memoEntry = new MemoEntry(null, position); 109 | bool first = true; 110 | IEnumerable answer; 111 | while (true) 112 | { 113 | // Reset the position 114 | Position = position; 115 | 116 | // Update the invocation set and call stack 117 | InvocationSet[position][nonterminal.Index] = true; 118 | CallStack.Push(nonterminal); 119 | 120 | // Evaluate the nonterminal 121 | answer = nonterminal.Eval(this); 122 | 123 | // Reset the invocation set and call stack 124 | CallStack.Pop(); 125 | InvocationSet[position][nonterminal.Index] = false; 126 | 127 | // Except for the first iteration, we never want to continue the iteration (or the loop) if the 128 | // evaluation failed to increase the Position. (The first time through we do need to finish the 129 | // iteration as the rest of the code takes care of bookkeeping required to note a failed attempt.) 130 | if (memoEntry.Position >= Position && !first) 131 | { 132 | // Update the position and answer since we are going to revert back to the state in the memo entry 133 | Position = memoEntry.Position; 134 | answer = memoEntry.Answer; 135 | break; 136 | } 137 | 138 | bool processing = false; 139 | 140 | // If we have an answer (and it's not the special LeftRecursion failure), then we want to store the 141 | // result in the memo table. If it is the special LeftRecursion failure, we don't because that's the 142 | // whole reason for the special value. 143 | if (answer != null && answer != LeftRecursion) 144 | { 145 | // We want to catch any case where the result was overwritten, as that fact invalidates the current 146 | // nonterminal's parse and will force a new iteration of the parse loop. 147 | if (memoEntry.Answer != null && memoEntry.Position < Position) 148 | { 149 | processing = true; 150 | } 151 | 152 | // Update the memo entry 153 | memoEntry.Position = Position; 154 | memoEntry.Answer = answer; 155 | MemoTable[position, nonterminal.Index] = memoEntry; 156 | } 157 | 158 | first = false; 159 | 160 | // We want to end the loop as early as possible. For the vast majority of cases, there is no left-recursion going 161 | // on at the current context (position+nonterminal). For these cases, we do not want to evaluate the nonterminal more 162 | // than once. Therefore, the loop will break if the following conditions are true: 163 | // 164 | // a) We are not specifically forcing a new iteration as specified above (processing) 165 | // b) There is absolutely no left-recursion going on or there is, but this nonterminal is not the one being 166 | // recursed. 167 | if (!processing && (LeftRecursionSet[position] == null || LeftRecursionSet[position].Rule != nonterminal)) 168 | break; 169 | } 170 | 171 | // Finally, record a failure if we found nothing even after a left-recursive attempt. 172 | if (answer == null && answer != LeftRecursion) 173 | { 174 | memoEntry.Answer = answer; 175 | memoEntry.Position = position; 176 | MemoTable[position, nonterminal.Index] = memoEntry; 177 | } 178 | 179 | // Reverses the left-recursion process described above so that the LeftRecursionSet is always kept in an 180 | // accurate state: If this was the top of the LeftRecursion stack, the set is cleared, otherwise the 181 | // current LeftRecursion is popped and the previous one restored. 182 | leftRecursion = LeftRecursionSet[position]; 183 | if (leftRecursion != null && leftRecursion.Rule == nonterminal) 184 | { 185 | leftRecursion = leftRecursion.Next; 186 | LeftRecursionSet[position] = leftRecursion; 187 | } 188 | 189 | return answer; 190 | } 191 | } 192 | else 193 | { 194 | Position = memoEntry.Position; 195 | return memoEntry.Answer; 196 | } 197 | } 198 | finally 199 | { 200 | Depth--; 201 | } 202 | } 203 | 204 | public override bool IsFailure(IEnumerable output) 205 | { 206 | return output == null || output == LeftRecursion; 207 | } 208 | 209 | public ParseException CreateException() 210 | { 211 | Terminal terminal = Input[MaxPosition]; 212 | int realPosition = MaxPosition; 213 | if (terminal is Token) 214 | realPosition = ((Token)terminal).Position; 215 | return new ParseException(RawInput, realPosition); 216 | } 217 | } 218 | } -------------------------------------------------------------------------------- /PEG/MemoEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG 4 | { 5 | public enum MemoStatus { Failed, Success } 6 | 7 | public class MemoEntry 8 | { 9 | public IEnumerable Answer { get; set; } 10 | public int Position { get; set; } 11 | 12 | public MemoEntry(IEnumerable output, int position) 13 | { 14 | Answer = output; 15 | Position = position; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /PEG/NonterminalReplacementTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.Cst; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG 6 | { 7 | public class NonterminalReplacementTarget : IReplacementTarget 8 | { 9 | private Nonterminal nonterminal; 10 | 11 | public NonterminalReplacementTarget(Nonterminal nonterminal) 12 | { 13 | this.nonterminal = nonterminal; 14 | } 15 | 16 | public ICstNode Replace(CstCache cache) 17 | { 18 | return cache[nonterminal][0]; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /PEG/OutputRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.SyntaxTree; 3 | 4 | namespace PEG 5 | { 6 | public enum OutputType { None, Begin, End } 7 | 8 | public class OutputRecord 9 | { 10 | public OutputType OutputType { get; set; } 11 | public Expression Expression { get; set; } 12 | public int Position { get; set;} 13 | 14 | public OutputRecord(Terminal expression, int position) 15 | { 16 | Expression = expression; 17 | Position = position; 18 | } 19 | 20 | public OutputRecord(Nonterminal expression, OutputType outputType, int position) 21 | { 22 | Expression = expression; 23 | OutputType = outputType; 24 | Position = position; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | switch (OutputType) 30 | { 31 | case OutputType.Begin: 32 | return "Begin " + ((Nonterminal)Expression).Name; 33 | case OutputType.End: 34 | return "End " + ((Nonterminal)Expression).Name; 35 | case OutputType.None: 36 | return Expression.ToString(); 37 | default: 38 | throw new InvalidOperationException(); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /PEG/PEG.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1;netstandard2.0;net461 5 | true 6 | PEG 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PEG/ParseEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using PEG.SyntaxTree; 5 | using PEG.Utils; 6 | 7 | namespace PEG 8 | { 9 | /// 10 | /// Implements the memoization algorithm for grammar rules that forms the core of the PEG parser. 11 | /// The characteristics of the individual operators of the syntax are implemented in the classes in 12 | /// the syntax tree. (Common.Core.Parsing.Peg.SyntaxTree) The Nonterminal class enjoys a special 13 | /// relationship with this class, and directly invokes the ApplyNonterminal method to implement its 14 | /// behavior. ParseEngine in turn calls back into the Nonterminal.Eval method, allowing that class 15 | /// to handle the bookkeeping of writing the begin and end markers to the output stream. 16 | /// 17 | public class ParseEngine 18 | { 19 | /// 20 | /// The actual string input. 21 | /// 22 | public string RawInput { get; set; } 23 | 24 | /// 25 | /// The grammar 26 | /// 27 | public Grammar Grammar { get; set; } 28 | 29 | /// 30 | /// An implementation of IParseInput that could either represent a raw text input, or the 31 | /// result of a tokenization pass. 32 | /// 33 | public IParseInput Input { get; private set; } 34 | 35 | /// 36 | /// The current position within Input. 37 | /// 38 | public int Position { get; protected set; } 39 | 40 | /// 41 | /// The recursion depth 42 | /// 43 | public int Depth { get; protected set; } 44 | 45 | /// 46 | /// Sometimes a Terminal can be created on the fly (for example, when using the AnyCharacter 47 | /// expression.) This allows those terminals to still be cached and avoid recreating tons 48 | /// of terminals for the same character. 49 | /// 50 | public DynamicArray TerminalsCache { get; private set; } 51 | 52 | /// 53 | /// The heart of any PEG parser, this is the memo table that caches the results of every possible 54 | /// invocation of a nonterminal at a given position. The first index is the nonterminal, and the 55 | /// second index is the position in the input. 56 | /// 57 | public MemoEntry[,] MemoTable { get; set; } 58 | 59 | /// 60 | /// The farthest point the parser reached. Usually this is a pretty good indicator of precisely 61 | /// where the parse failed. 62 | /// 63 | public int MaxPosition { get; set; } 64 | 65 | public bool IsLogEnabled { get; set; } 66 | 67 | private List>> interceptorStack = new List>>(); 68 | 69 | public ParseEngine(Grammar grammar, string input) 70 | { 71 | Grammar = grammar; 72 | Input = new StringParseInput(this, input); 73 | RawInput = input; 74 | Init(); 75 | } 76 | 77 | public ParseEngine(Grammar grammar, string rawInput, IParseInput input) 78 | { 79 | Grammar = grammar; 80 | Input = input; 81 | RawInput = rawInput; 82 | Init(); 83 | } 84 | 85 | private void Init() 86 | { 87 | TerminalsCache = new DynamicArray(); 88 | MemoTable = new MemoEntry[Input.Length + 1, Grammar.Nonterminals.Count]; 89 | IsLogEnabled = true; 90 | } 91 | 92 | public const string Indent = " "; 93 | 94 | [Conditional("LOG")] 95 | public void Log(string message) 96 | { 97 | if (!IsLogEnabled) 98 | return; 99 | 100 | for (int i = 0; i < Depth; i++) 101 | Console.Write(Indent); 102 | Console.WriteLine(message); 103 | } 104 | 105 | /// 106 | /// Current input symbol 107 | /// 108 | public Terminal Current 109 | { 110 | get { return Position < Input.Length ? Input[Position] : null; } 111 | } 112 | 113 | /// 114 | /// Abstracts the idea of marking and resetting in case we ever need to keep track of 115 | /// more than the position. (for now, merely returns the position) 116 | /// 117 | /// 118 | public int Mark() 119 | { 120 | return Position; 121 | } 122 | 123 | private bool isIntercepting; 124 | 125 | /// 126 | /// Increments the Position by 1. 127 | /// 128 | public bool Consume() 129 | { 130 | Log("Consumed " + Current.ToString()); 131 | 132 | if (interceptorStack.Count > 0 && !isIntercepting) 133 | { 134 | isIntercepting = true; 135 | for (int i = interceptorStack.Count - 1; i >= 0; i--) 136 | { 137 | var predicate = interceptorStack[i]; 138 | if (!IsFailure(predicate())) 139 | return false; 140 | } 141 | isIntercepting = false; 142 | } 143 | 144 | Position++; 145 | if (Position > MaxPosition) 146 | MaxPosition = Position; 147 | 148 | return true; 149 | } 150 | 151 | /// 152 | /// Restores the state of the parser to the previous mark. (for now, merely restores the 153 | /// Position) 154 | /// 155 | /// 156 | public void Reset(int mark) 157 | { 158 | Position = mark; 159 | } 160 | 161 | /// 162 | /// Invokes the nonterminal, and stores the result in the memo table. 163 | /// 164 | /// 165 | /// 166 | /// 167 | public virtual IEnumerable ApplyNonterminal(Nonterminal nonterminal, int position) 168 | { 169 | Depth++; 170 | try 171 | { 172 | MemoEntry memoEntry = MemoTable[position, nonterminal.Index]; 173 | if (memoEntry == null) 174 | { 175 | var answer = nonterminal.Eval(this); 176 | memoEntry = new MemoEntry(answer, Position); 177 | MemoTable[position, nonterminal.Index] = memoEntry; 178 | return answer; 179 | } 180 | else 181 | { 182 | Position = memoEntry.Position; 183 | return memoEntry.Answer; 184 | } 185 | } 186 | finally 187 | { 188 | Depth--; 189 | } 190 | } 191 | 192 | /// 193 | /// Returns true if output is null. Subclasses can override this method to 194 | /// expand on the definition of a failure. 195 | /// 196 | public virtual bool IsFailure(IEnumerable output) 197 | { 198 | return output == null; 199 | } 200 | 201 | public void AddInterceptor(Func> expression) 202 | { 203 | interceptorStack.Add(expression); 204 | } 205 | 206 | public void RemoveInterceptor(Func> expression) 207 | { 208 | interceptorStack.Remove(expression); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /PEG/ParseError.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using PEG.Extensions; 3 | 4 | namespace PEG 5 | { 6 | public class ParseError 7 | { 8 | public string Input { get; set; } 9 | public int Position { get; set; } 10 | 11 | public ParseError(string input, int position) 12 | { 13 | Input = input; 14 | Position = position; 15 | } 16 | 17 | public override string ToString() 18 | { 19 | int offset; 20 | string line = Input.GetLine(Position, out offset); 21 | int oldLineLength = line.Length; 22 | line = line.Replace("\t", "\\t"); 23 | offset += line.Length - oldLineLength; 24 | 25 | StringBuilder builder = new StringBuilder(); 26 | 27 | builder.AppendLine("Parse Failure"); 28 | builder.AppendLine(line); 29 | 30 | for (int i = 0; i < offset; i++) 31 | builder.Append(' '); 32 | builder.Append('^'); 33 | builder.AppendLine(); 34 | 35 | builder.AppendLine("Current Input Character: " + Input[Position]); 36 | 37 | return builder.ToString(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /PEG/ParseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG 4 | { 5 | public class ParseException : Exception 6 | { 7 | public ParseException(string input, int position) : base(new ParseError(input, position).ToString()) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /PEG/Peg.cs: -------------------------------------------------------------------------------- 1 | using PEG.SyntaxTree; 2 | 3 | namespace PEG 4 | { 5 | public class Peg 6 | { 7 | public static AnyCharacter Any = new AnyCharacter(); 8 | } 9 | } -------------------------------------------------------------------------------- /PEG/PegParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using PEG.Builder; 5 | using PEG.SyntaxTree; 6 | 7 | namespace PEG 8 | { 9 | public class PegParser 10 | { 11 | public Grammar Grammar { get; set; } 12 | public Expression StartExpression { get; set; } 13 | 14 | public PegParser(Grammar grammar) 15 | { 16 | Grammar = grammar; 17 | } 18 | 19 | public PegParser(Grammar grammar, Expression startExpression) 20 | { 21 | Grammar = grammar; 22 | StartExpression = startExpression; 23 | } 24 | 25 | public IEnumerable ParseString(string input, bool requireCompleteParse = true) 26 | { 27 | LrParseEngine parseEngine = new LrParseEngine(Grammar, input); 28 | Expression startExpression = StartExpression ?? Grammar.StartExpression; 29 | IEnumerable outputRecords = startExpression.Execute(parseEngine); 30 | if (outputRecords != null && ((outputRecords.Any() && parseEngine.Position < input.Length && requireCompleteParse) || !outputRecords.Any())) 31 | outputRecords = null; 32 | return outputRecords; 33 | } 34 | } 35 | 36 | public class PegParser : PegParser 37 | { 38 | public PegBuilder Builder { get; set; } 39 | 40 | public PegParser(Grammar grammar) : this(grammar, grammar.Nonterminals[0]) 41 | { 42 | } 43 | 44 | public PegParser(Grammar grammar, Expression startExpression) : base(grammar, startExpression) 45 | { 46 | Builder = new PegBuilder(Grammar); 47 | } 48 | 49 | public PegParser(Grammar grammar, Expression startExpression, PegBuilder builder) : base(grammar, startExpression) 50 | { 51 | Builder = builder; 52 | } 53 | 54 | public T Parse(string input) 55 | { 56 | int amountRead; 57 | return Parse(input, out amountRead); 58 | } 59 | 60 | public T Parse(string input, out int amountRead) 61 | { 62 | LrParseEngine parseEngine = CreateParseEngine(input); 63 | Expression startExpression = StartExpression ?? Grammar.StartExpression; 64 | T result = Parse(parseEngine, startExpression); 65 | amountRead = parseEngine.Position; 66 | return result; 67 | } 68 | 69 | public bool Parse(string input, out T result, out int amountRead) 70 | { 71 | LrParseEngine parseEngine = CreateParseEngine(input); 72 | Expression startExpression = StartExpression ?? Grammar.StartExpression; 73 | bool returnValue = Parse(parseEngine, startExpression, out result); 74 | amountRead = returnValue ? parseEngine.Position : 0; 75 | return returnValue; 76 | } 77 | 78 | private T Parse(LrParseEngine parseEngine, Expression startExpression) 79 | { 80 | IEnumerable outputStream = startExpression.Execute(parseEngine); 81 | if (!parseEngine.IsFailure(outputStream)) 82 | { 83 | T result = Builder.Build(outputStream); 84 | return result; 85 | } 86 | else 87 | { 88 | throw parseEngine.CreateException(); 89 | } 90 | } 91 | 92 | private bool Parse(LrParseEngine parseEngine, Expression startExpression, out T result) 93 | { 94 | IEnumerable outputStream = startExpression.Execute(parseEngine); 95 | if (!parseEngine.IsFailure(outputStream)) 96 | { 97 | result = new PegBuilder(Grammar).Build(outputStream); 98 | return true; 99 | } 100 | else 101 | { 102 | result = default(T); 103 | return false; 104 | } 105 | } 106 | 107 | private LrParseEngine CreateParseEngine(string input) 108 | { 109 | LrParseEngine parseEngine; 110 | if (Grammar.Tokenizer != null) 111 | { 112 | parseEngine = Tokenize(input); 113 | } 114 | else 115 | { 116 | parseEngine = new LrParseEngine(Grammar, input); 117 | } 118 | return parseEngine; 119 | } 120 | 121 | private LrParseEngine Tokenize(string input) 122 | { 123 | LrParseEngine tokenizerEngine = new LrParseEngine(Grammar.TokenizerGrammar.TokenizerGrammar, input); 124 | Nonterminal tokenizer = Grammar.Tokenizer; 125 | List tokenOutput = new List(); 126 | int absoluteIndex = 0; 127 | while (tokenizerEngine.Position < input.Length) 128 | { 129 | int position = tokenizerEngine.Position; 130 | IEnumerable output = tokenizer.Execute(tokenizerEngine); 131 | if (output != null) 132 | { 133 | Nonterminal rule = output.Select(o => o.Expression).OfType().Where(o => o.IsToken).First(); 134 | if (!rule.IsTokenOmitted) 135 | { 136 | // Trim tokenizer 137 | output = TrimOutput(output); 138 | 139 | Token token = new Token(rule, output, absoluteIndex++); 140 | token.Position = position; 141 | tokenOutput.Add(token); 142 | } 143 | } 144 | // If the tokenization iteration did not move the position forward at all, 145 | // then the current character is not part of a valid token. Rather than 146 | // considering this an error, we will just add the character into the next 147 | // input stream as-is -- as a CharacterTerminal. This is useful for tokens 148 | // that are context sensitive. An example is the C# shift-right operator 149 | // together with closing two generic types at once. Both will make use of 150 | // the character sequence ">>". However, in the case of the shift-right 151 | // operation, the "token" should be the full "<<" -- both characters. On 152 | // the other hand, when closing a generic type, the token should be a single 153 | // '>' (followed in this case by another '>' immediately after.) The best way 154 | // to disambiguate this situation is to not create a token and allow the full 155 | // grammar to treat the actual characters (hence the full grammar will actually 156 | // contain raw characters whereas a grammar dependent on a fully tokenized (as 157 | // opposed to partially tokenized) input would not have any terminals in its 158 | // grammar except foreign references to the tokens defined in the token grammar. 159 | if (tokenizerEngine.Position == position) 160 | { 161 | tokenOutput.Add(new CharacterTerminal(input[position])); 162 | if (!tokenizerEngine.Consume()) 163 | throw new InvalidOperationException("Not sure what to do if this happens"); 164 | } 165 | } 166 | return new LrParseEngine(Grammar, input, new TokenParseInput(tokenOutput.ToArray())); 167 | } 168 | 169 | private IEnumerable TrimOutput(IEnumerable source) 170 | { 171 | var enumerator = source.GetEnumerator(); 172 | enumerator.MoveNext(); // Now at beginning 173 | enumerator.MoveNext(); // Now past first entry 174 | 175 | OutputRecord last = null; 176 | OutputRecord secondLast = null; 177 | while (enumerator.MoveNext()) 178 | { 179 | if (secondLast != null) 180 | yield return secondLast; 181 | secondLast = last; 182 | last = enumerator.Current; 183 | } 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /PEG/Proxies/Extensions/EmitExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | namespace PEG.Proxies.Extensions 7 | { 8 | public static class EmitExtensions 9 | { 10 | public static void EmitDefaultBaseConstructorCall(this ILGenerator il, Type baseType) 11 | { 12 | Type constructorType = baseType; 13 | ConstructorInfo conObj = null; 14 | while (conObj == null) 15 | { 16 | constructorType = (constructorType == null ? baseType : constructorType.BaseType) ?? typeof(object); 17 | conObj = constructorType.GetConstructor(new Type[0]); 18 | } 19 | 20 | il.Emit(OpCodes.Ldarg_0); 21 | il.Emit(OpCodes.Call, conObj); 22 | } 23 | 24 | static readonly MethodInfo GetTypeFromRuntimeHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle"); 25 | private static MethodInfo Type_GetMethod = typeof(Type).GetMethod("GetMethod", 26 | new[] { typeof(string), typeof(BindingFlags), typeof(Binder), typeof(Type[]), typeof(ParameterModifier[]) }); 27 | 28 | public static void LoadType(this ILGenerator il, Type type) 29 | { 30 | il.Emit(OpCodes.Ldtoken, type); 31 | il.Emit(OpCodes.Call, GetTypeFromRuntimeHandleMethod); 32 | } 33 | 34 | public static void StoreMethodInfo(this ILGenerator il, FieldBuilder staticField, MethodInfo method) 35 | { 36 | Type[] parameterTypes = method.GetParameters().Select(info => info.ParameterType).ToArray(); 37 | 38 | // The type we want to invoke GetMethod upon 39 | il.LoadType(method.DeclaringType); 40 | 41 | // Arg1: methodName 42 | il.Emit(OpCodes.Ldstr, method.Name); 43 | 44 | // Arg2: bindingFlags 45 | il.Emit(OpCodes.Ldc_I4, (int)(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)); 46 | 47 | // Arg3: binder 48 | il.Emit(OpCodes.Ldnull); 49 | 50 | // Arg4: parameterTypes 51 | il.Emit(OpCodes.Ldc_I4, parameterTypes.Length); 52 | il.Emit(OpCodes.Newarr, typeof(Type)); 53 | // Copy array for each element we are going to set 54 | for (int i = 0; i < parameterTypes.Length; i++) 55 | { 56 | il.Emit(OpCodes.Dup); 57 | } 58 | // Set each element 59 | for (int i = 0; i < parameterTypes.Length; i++) 60 | { 61 | il.Emit(OpCodes.Ldc_I4, i); 62 | il.LoadType(parameterTypes[i]); 63 | il.Emit(OpCodes.Stelem, typeof(Type)); 64 | } 65 | 66 | // Arg5: parameterModifiers 67 | il.Emit(OpCodes.Ldnull); 68 | 69 | // Invoke method 70 | il.EmitCall(OpCodes.Call, Type_GetMethod, null); 71 | 72 | // Store MethodInfo into the static field 73 | il.Emit(OpCodes.Stsfld, staticField); 74 | } 75 | 76 | public static void EmitDefaultValue(this ILGenerator il, Type type) 77 | { 78 | if (type == typeof(bool) || type == typeof(byte) || type == typeof(short) || type == typeof(int)) 79 | il.Emit(OpCodes.Ldc_I4_0); 80 | else if (type == typeof(float)) 81 | il.Emit(OpCodes.Ldc_R4, (float)0); 82 | else if (type == typeof(long)) 83 | il.Emit(OpCodes.Ldc_I8); 84 | else if (type == typeof(double)) 85 | il.Emit(OpCodes.Conv_R8); 86 | else if (type.IsValueType) 87 | { 88 | // var local = il.DeclareLocal(type); 89 | // il.Emit(OpCodes.Stloc, local); 90 | il.Emit(OpCodes.Ldsfld, type.GetField("Empty")); 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /PEG/Proxies/Invocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace PEG.Proxies 5 | { 6 | public class Invocation 7 | { 8 | public MethodInfo Method { get; private set; } 9 | public object ReturnValue { get; set; } 10 | public object[] Arguments { get; set; } 11 | 12 | private Func implementation; 13 | 14 | public Invocation(MethodInfo method, object[] arguments, Func implementation) 15 | { 16 | Method = method; 17 | Arguments = arguments; 18 | this.implementation = implementation; 19 | } 20 | 21 | public void Proceed() 22 | { 23 | ReturnValue = implementation(Arguments); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /PEG/Replacement.cs: -------------------------------------------------------------------------------- 1 | using PEG.SyntaxTree; 2 | 3 | namespace PEG 4 | { 5 | public class Replacement 6 | { 7 | public Nonterminal From { get; set; } 8 | public IReplacementTarget To { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /PEG/StringParseInput.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG 6 | { 7 | public class StringParseInput : IParseInput 8 | { 9 | private string data; 10 | private ParseEngine engine; 11 | 12 | public StringParseInput(ParseEngine engine, string data) 13 | { 14 | this.engine = engine; 15 | this.data = data; 16 | } 17 | 18 | public Terminal this[int index] 19 | { 20 | get 21 | { 22 | Terminal result = engine.TerminalsCache[data[index]]; 23 | if (result == null) 24 | { 25 | result = new CharacterTerminal(data[index]); 26 | engine.TerminalsCache[data[index]] = result; 27 | } 28 | return result; 29 | } 30 | } 31 | 32 | public int Length 33 | { 34 | get { return data.Length; } 35 | } 36 | 37 | IEnumerator IEnumerable.GetEnumerator() 38 | { 39 | return GetEnumerator(); 40 | } 41 | 42 | public IEnumerator GetEnumerator() 43 | { 44 | for (int i = 0; i < Length; i++) 45 | yield return this[i]; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /PEG/StringReplacementTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PEG.Cst; 3 | 4 | namespace PEG 5 | { 6 | public class StringReplacementTarget : IReplacementTarget 7 | { 8 | private ICstNode s; 9 | 10 | public StringReplacementTarget(string s) 11 | { 12 | this.s = new CstString(s); 13 | } 14 | 15 | public ICstNode Replace(CstCache cache) 16 | { 17 | return s; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/AndPredicate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class AndPredicate : Expression 6 | { 7 | public Expression Operand { get; set; } 8 | 9 | public override IEnumerable Execute(ParseEngine engine) 10 | { 11 | var mark = engine.Mark(); 12 | IEnumerable result = Operand.Execute(engine); 13 | engine.Reset(mark); 14 | return !engine.IsFailure(result) ? NoResults : null; 15 | } 16 | 17 | public override void Accept(IExpressionVisitor visitor, T context) 18 | { 19 | visitor.Visit(this, context); 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return "And(" + Operand.ToString() + ")"; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/AnyCharacter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class AnyCharacter : Expression 7 | { 8 | public bool OutputCharacters { get; set; } 9 | 10 | public AnyCharacter() 11 | { 12 | OutputCharacters = true; 13 | } 14 | 15 | public AnyCharacter(bool outputCharacters) 16 | { 17 | OutputCharacters = outputCharacters; 18 | } 19 | 20 | public override IEnumerable Execute(ParseEngine engine) 21 | { 22 | if (engine.Current == null) 23 | return null; 24 | if (OutputCharacters) 25 | return engine.Current.Execute(engine); 26 | else if (engine.Current.Execute(engine).Any()) 27 | return NoResults; 28 | return null; 29 | } 30 | 31 | public override void Accept(IExpressionVisitor visitor, T context) 32 | { 33 | visitor.Visit(this, context); 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return "Any"; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/AnyCharacterTerminal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | internal class AnyCharacterTerminal : Terminal 7 | { 8 | public static AnyCharacterTerminal Instance = new AnyCharacterTerminal(); 9 | 10 | public override IEnumerable Execute(ParseEngine engine) 11 | { 12 | throw new NotImplementedException(); 13 | } 14 | 15 | public override string Coalesce() 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/CharacterRange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class CharacterRange : CharacterSet 6 | { 7 | public char StartCharacter { get; set; } 8 | public char EndCharacter { get; set; } 9 | 10 | public CharacterRange(char startCharacter, char endCharacter, params CharacterTerminal[] characters) : base(characters) 11 | { 12 | StartCharacter = startCharacter; 13 | EndCharacter = endCharacter; 14 | } 15 | 16 | public CharacterRange(char startCharacter, char endCharacter, IEnumerable characters) : base(characters) 17 | { 18 | StartCharacter = startCharacter; 19 | EndCharacter = endCharacter; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return string.Format("'{0}'.To('{1}')", StartCharacter, EndCharacter); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/CharacterSet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class CharacterSet : Expression 7 | { 8 | public IEnumerable Characters { get; set; } 9 | public CharacterTerminal[] CharacterTable { get; set; } 10 | 11 | public CharacterSet(params CharacterTerminal[] characters) : this((IEnumerable)characters) 12 | { 13 | } 14 | 15 | public CharacterSet(IEnumerable characters) 16 | { 17 | Characters = characters; 18 | 19 | CharacterTerminal[] orderedCharacters = characters.OrderBy(o => o.Character).ToArray(); 20 | int maxCharacter = orderedCharacters.Last().Character; 21 | CharacterTable = new CharacterTerminal[maxCharacter + 1]; 22 | foreach (CharacterTerminal c in orderedCharacters) 23 | CharacterTable[c.Character] = c; 24 | } 25 | 26 | public override IEnumerable Execute(ParseEngine engine) 27 | { 28 | Terminal current = engine.Current; 29 | if (current is CharacterTerminal && ((CharacterTerminal)current).Character < CharacterTable.Length && CharacterTable[((CharacterTerminal)current).Character] != null) 30 | return current.Execute(engine); 31 | else 32 | return null; 33 | } 34 | 35 | public override void Accept(IExpressionVisitor visitor, T context) 36 | { 37 | visitor.Visit(this, context); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/CharacterTerminal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class CharacterTerminal : Terminal 6 | { 7 | public char Character { get; private set; } 8 | 9 | public CharacterTerminal(char character) 10 | { 11 | Character = character; 12 | } 13 | 14 | public override string ToString() 15 | { 16 | return "'" + Character + "'"; 17 | } 18 | 19 | public override string Coalesce() 20 | { 21 | return Character.ToString(); 22 | } 23 | 24 | public override IEnumerable Execute(ParseEngine engine) 25 | { 26 | if (Equals(engine.Current)) 27 | { 28 | if (engine.Consume()) 29 | return AsResult(engine.Position); 30 | else 31 | return null; 32 | } 33 | return null; 34 | } 35 | 36 | public bool Equals(CharacterTerminal other) 37 | { 38 | if (ReferenceEquals(null, other)) return false; 39 | if (ReferenceEquals(this, other)) return true; 40 | return other.Character == Character; 41 | } 42 | 43 | public override bool Equals(object obj) 44 | { 45 | if (ReferenceEquals(null, obj)) return false; 46 | if (ReferenceEquals(this, obj)) return true; 47 | if (obj.GetType() != typeof(CharacterTerminal)) return false; 48 | return Equals((CharacterTerminal)obj); 49 | } 50 | 51 | public override int GetHashCode() 52 | { 53 | return Character.GetHashCode(); 54 | } 55 | 56 | public static implicit operator CharacterTerminal(char c) 57 | { 58 | return new CharacterTerminal(c); 59 | } 60 | 61 | public override void Accept(IExpressionVisitor visitor, T context) 62 | { 63 | visitor.Visit(this, context); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/EmptyString.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class EmptyString : Expression 6 | { 7 | public override IEnumerable Execute(ParseEngine engine) 8 | { 9 | return NoResults; 10 | } 11 | 12 | public override void Accept(IExpressionVisitor visitor, T context) 13 | { 14 | visitor.Visit(this, context); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/EncloseExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace PEG.SyntaxTree 6 | { 7 | public class EncloseExpression : Expression 8 | { 9 | public Expression Enclosure { get; set; } 10 | public Expression Operand { get; set; } 11 | 12 | public override IEnumerable Execute(ParseEngine engine) 13 | { 14 | var current = Enclosure.Execute(engine); 15 | if (engine.IsFailure(current)) 16 | return current; 17 | 18 | Func> predicate = () => 19 | { 20 | var mark = engine.Mark(); 21 | var result = Enclosure.Execute(engine); 22 | engine.Reset(mark); 23 | return result; 24 | }; 25 | 26 | engine.AddInterceptor(predicate); 27 | try 28 | { 29 | var operandResult = Operand.Execute(engine); 30 | if (engine.IsFailure(operandResult)) 31 | return operandResult; 32 | 33 | current = current.Concat(operandResult); 34 | } 35 | finally 36 | { 37 | engine.RemoveInterceptor(predicate); 38 | } 39 | 40 | var closeResult = Enclosure.Execute(engine); 41 | if (engine.IsFailure(closeResult)) 42 | return closeResult; 43 | 44 | return current.Concat(closeResult); 45 | } 46 | 47 | public override void Accept(IExpressionVisitor visitor, T context) 48 | { 49 | visitor.Visit(this, context); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Expression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | 6 | namespace PEG.SyntaxTree 7 | { 8 | /// 9 | /// Base class for all PEG expressions. 10 | /// 11 | /// The operator overloading contained here attempts to approximate the actual PEG grammar within 12 | /// the syntax of C#. The normal PEG operators have been changed as follows: 13 | /// 14 | /// sequence: No operator, just expressions followed in succession. 15 | /// This would be illegal in C# so the '+' operator is used to separate expressions in a sequence. 16 | /// ordered choice: '/' 17 | /// The precedence of this operator in C# would be far too high so has been changed to the traditional 18 | /// pipe character ('|') which has very low precedence. 19 | /// one or more: '+' 20 | /// In C# it's the same, but the unary operator must precede the expression, rather than be a suffix. 21 | /// i.e. +a instead of a+. 22 | /// zero or more: '*' 23 | /// In C#, '*' is not a valid unary operator, so has been changed to '-'. Use as a prefix. 24 | /// optional: '?' 25 | /// In C#, '?' is not a valid unary operator, so has been changed to '~'. Again, as a prefix. 26 | /// and predicate: '&' 27 | /// We have run out of operators, so we will use a extension method named "And". 28 | /// not predicate: '!' 29 | /// Exactly the same. 30 | /// 31 | /// 32 | public abstract class Expression 33 | { 34 | public abstract IEnumerable Execute(ParseEngine engine); 35 | public abstract void Accept(IExpressionVisitor visitor, T context); 36 | 37 | protected static IEnumerable NoResults = Enumerable.Empty(); 38 | 39 | public static implicit operator Expression(char c) 40 | { 41 | return new CharacterTerminal(c); 42 | } 43 | 44 | public static implicit operator Expression(string seq) 45 | { 46 | return (Sequence)seq; 47 | } 48 | 49 | public static Expression operator +(Expression left, char right) 50 | { 51 | return left + (Terminal)right; 52 | } 53 | 54 | public static Expression operator +(char left, Expression right) 55 | { 56 | return (Terminal)left + right; 57 | } 58 | 59 | public static Expression operator +(Expression left, Expression right) 60 | { 61 | // Coalesce nested sequences 62 | if (left is Sequence) 63 | { 64 | Sequence existing = (Sequence)left; 65 | existing.Expressions.Add(right); 66 | return existing; 67 | } 68 | else 69 | { 70 | Sequence sequence = new Sequence(); 71 | sequence.Expressions.Add(left); 72 | sequence.Expressions.Add(right); 73 | return sequence; 74 | } 75 | } 76 | 77 | public static Expression operator |(Expression left, char right) 78 | { 79 | return left | (Terminal)right; 80 | } 81 | 82 | public static Expression operator |(char left, Expression right) 83 | { 84 | return (Terminal)left | right; 85 | } 86 | 87 | public static Expression operator |(Expression left, string right) 88 | { 89 | return left | (Sequence)right; 90 | } 91 | 92 | public static Expression operator |(string left, Expression right) 93 | { 94 | return (Sequence)left | right; 95 | } 96 | 97 | public static Expression operator |(Expression left, Expression right) 98 | { 99 | // Coalesce nested ordered choices 100 | if (left is OrderedChoice) 101 | { 102 | OrderedChoice existing = (OrderedChoice)left; 103 | existing.Expressions.Add(right); 104 | return existing; 105 | } 106 | else 107 | { 108 | OrderedChoice orderedChoice = new OrderedChoice(); 109 | orderedChoice.Expressions.Add(left); 110 | orderedChoice.Expressions.Add(right); 111 | return orderedChoice; 112 | } 113 | } 114 | 115 | /// 116 | /// Applies the one-or-more operator onto the operand (the expression that 117 | /// follows this operator). 118 | /// 119 | public static Expression operator +(Expression operand) 120 | { 121 | OneOrMore oneOrMore = new OneOrMore(); 122 | oneOrMore.Operand = operand; 123 | return oneOrMore; 124 | } 125 | 126 | public static Expression operator -(Expression operand) 127 | { 128 | ZeroOrMore zeroOrMore = new ZeroOrMore(); 129 | zeroOrMore.Operand = operand; 130 | return zeroOrMore; 131 | } 132 | 133 | public static Expression operator !(Expression operand) 134 | { 135 | NotPredicate notPredicate = new NotPredicate(); 136 | notPredicate.Operand = operand; 137 | return notPredicate; 138 | } 139 | 140 | public static Expression operator ~(Expression operand) 141 | { 142 | Optional optional = new Optional(); 143 | optional.Operand = operand; 144 | return optional; 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/ForeignNonterminal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class ForeignNonterminal : Expression 6 | { 7 | public string Name { get; set; } 8 | 9 | public override IEnumerable Execute(ParseEngine engine) 10 | { 11 | Token token = engine.Current as Token; 12 | if (token != null) 13 | { 14 | if (token.TokenRule.Name == Name) 15 | { 16 | return token.Execute(engine); 17 | } 18 | } 19 | return null; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return Name; 25 | } 26 | 27 | public override void Accept(IExpressionVisitor visitor, T context) 28 | { 29 | visitor.Visit(this, context); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/IExpressionVisitor.cs: -------------------------------------------------------------------------------- 1 | namespace PEG.SyntaxTree 2 | { 3 | public interface IExpressionVisitor 4 | { 5 | void Visit(AndPredicate expression, T context); 6 | void Visit(AnyCharacter expression, T context); 7 | void Visit(CharacterSet expression, T context); 8 | void Visit(CharacterTerminal expression, T context); 9 | void Visit(EmptyString expression, T context); 10 | void Visit(Nonterminal expression, T context); 11 | void Visit(NotPredicate expression, T context); 12 | void Visit(OneOrMore expression, T context); 13 | void Visit(Optional expression, T context); 14 | void Visit(OrderedChoice expression, T context); 15 | void Visit(Sequence expression, T context); 16 | void Visit(Terminal expression, T context); 17 | void Visit(Token expression, T context); 18 | void Visit(ZeroOrMore expression, T context); 19 | void Visit(ForeignNonterminal expression, T context); 20 | void Visit(Substitution expression, T context); 21 | void Visit(Repeat expression, T context); 22 | void Visit(EncloseExpression expression, T context); 23 | } 24 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Nonterminal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class Nonterminal : Expression 6 | { 7 | public int Index { get; set; } 8 | public string Name { get; set; } 9 | public Expression Expression { get; set; } 10 | 11 | // When used as tokens 12 | public bool IsToken { get; set; } 13 | public bool IsTokenOmitted { get; set; } 14 | 15 | public Nonterminal() 16 | { 17 | } 18 | 19 | public Nonterminal(Expression expression) 20 | { 21 | Expression = expression; 22 | } 23 | 24 | public IEnumerable AsResult(IEnumerable children, int startPosition, int endPosition) 25 | { 26 | yield return new OutputRecord(this, OutputType.Begin, startPosition); 27 | foreach (OutputRecord child in children) 28 | yield return child; 29 | yield return new OutputRecord(this, OutputType.End, endPosition); 30 | } 31 | 32 | public IEnumerable Eval(ParseEngine context) 33 | { 34 | int startPosition = context.Position; 35 | IEnumerable children = Expression.Execute(context); 36 | if (context.IsFailure(children)) 37 | return children; 38 | else 39 | { 40 | int endPosition = context.Position; 41 | return AsResult(children, startPosition, endPosition); 42 | } 43 | } 44 | 45 | public override IEnumerable Execute(ParseEngine context) 46 | { 47 | context.Log(ToString()); 48 | context.Log("{"); 49 | // if (!context.Grammar.IsTokenGrammar) 50 | // Console.WriteLine("Executing " + Name); 51 | IEnumerable nonterminal = context.ApplyNonterminal(this, context.Position); 52 | // if (!context.Grammar.IsTokenGrammar) 53 | // Console.WriteLine("Executed " + Name); 54 | // if (!context.IsFailure(nonterminal) && !context.Grammar.IsTokenGrammar) 55 | // Logger.WriteLine("Accepted " + Name + " (" + nonterminal.Concatenate(" ", o => o.OutputType == OutputType.None ? o.ToString() : "") + ")"); 56 | 57 | if (context.IsFailure(nonterminal)) 58 | context.Log(ParseEngine.Indent + "Failed"); 59 | else 60 | context.Log(ParseEngine.Indent + "Succeeded"); 61 | 62 | context.Log("}"); 63 | 64 | return nonterminal; 65 | } 66 | 67 | public override string ToString() 68 | { 69 | return Name; 70 | } 71 | 72 | public override void Accept(IExpressionVisitor visitor, T context) 73 | { 74 | visitor.Visit(this, context); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/NotPredicate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class NotPredicate : Expression 6 | { 7 | public Expression Operand { get; set; } 8 | 9 | public override IEnumerable Execute(ParseEngine engine) 10 | { 11 | var mark = engine.Mark(); 12 | IEnumerable result = Operand.Execute(engine); 13 | IEnumerable returnValue; 14 | if (engine.IsFailure(result)) 15 | returnValue = NoResults; 16 | else 17 | returnValue = null; 18 | engine.Reset(mark); 19 | return returnValue; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return "!" + Operand.ToString(); 25 | } 26 | 27 | public override void Accept(IExpressionVisitor visitor, T context) 28 | { 29 | visitor.Visit(this, context); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/NullTerminal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | internal class NullTerminal : Terminal 7 | { 8 | public static NullTerminal Instance = new NullTerminal(); 9 | 10 | public override IEnumerable Execute(ParseEngine engine) 11 | { 12 | throw new NotImplementedException(); 13 | } 14 | 15 | public override string Coalesce() 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/OneOrMore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class OneOrMore : Expression 7 | { 8 | public Expression Operand { get; set; } 9 | 10 | public IEnumerable> GetResults(ParseEngine engine, IEnumerable first) 11 | { 12 | do 13 | { 14 | yield return first; 15 | } 16 | while (!engine.IsFailure(first = Operand.Execute(engine))); 17 | } 18 | 19 | public override IEnumerable Execute(ParseEngine engine) 20 | { 21 | IEnumerable first = Operand.Execute(engine); 22 | if (engine.IsFailure(first)) 23 | return first; 24 | else 25 | return GetResults(engine, first).ToArray().SelectMany(o => o); 26 | } 27 | 28 | public override string ToString() 29 | { 30 | return "+" + Operand.ToString(); 31 | } 32 | 33 | public override void Accept(IExpressionVisitor visitor, T context) 34 | { 35 | visitor.Visit(this, context); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Optional.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PEG.SyntaxTree 4 | { 5 | public class Optional : Expression 6 | { 7 | public Expression Operand { get; set; } 8 | 9 | public override IEnumerable Execute(ParseEngine engine) 10 | { 11 | IEnumerable result = Operand.Execute(engine); 12 | if (!engine.IsFailure(result)) 13 | return result; 14 | return NoResults; 15 | } 16 | 17 | public override string ToString() 18 | { 19 | return "~" + Operand.ToString(); 20 | } 21 | 22 | public override void Accept(IExpressionVisitor visitor, T context) 23 | { 24 | visitor.Visit(this, context); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/OrderedChoice.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.Extensions; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class OrderedChoice : Expression 7 | { 8 | public List Expressions { get; set; } 9 | 10 | public OrderedChoice() 11 | { 12 | Expressions = new List(); 13 | } 14 | 15 | public override IEnumerable Execute(ParseEngine engine) 16 | { 17 | IEnumerable nullResult = null; 18 | foreach (Expression expression in Expressions) 19 | { 20 | var current = expression.Execute(engine); 21 | if (!engine.IsFailure(current)) 22 | return current; 23 | else if (nullResult == null && current != null) 24 | nullResult = current; 25 | } 26 | return nullResult; 27 | } 28 | 29 | public override string ToString() 30 | { 31 | return "(" + Expressions.Delimit(" | ") + ")"; 32 | } 33 | 34 | public override void Accept(IExpressionVisitor visitor, T context) 35 | { 36 | visitor.Visit(this, context); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Repeat.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class Repeat : Expression 7 | { 8 | public Expression Operand { get; set; } 9 | public int Min { get; set; } 10 | public int Max { get; set; } 11 | 12 | public override IEnumerable Execute(ParseEngine engine) 13 | { 14 | var result = NoResults; 15 | IEnumerable current; 16 | 17 | var mark = engine.Mark(); 18 | 19 | int count; 20 | for (count = 0; !engine.IsFailure(current = Operand.Execute(engine)); count++) 21 | { 22 | if (count > Max) 23 | { 24 | engine.Reset(mark); 25 | return null; 26 | } 27 | 28 | result = result.Concat(current); 29 | } 30 | 31 | if (count < Min) 32 | { 33 | engine.Reset(mark); 34 | return null; 35 | } 36 | 37 | return result; 38 | } 39 | 40 | public override string ToString() 41 | { 42 | return Operand.ToString() + ".Repeat(" + Min + ", " + Max + ")"; 43 | } 44 | 45 | public override void Accept(IExpressionVisitor visitor, T context) 46 | { 47 | visitor.Visit(this, context); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Sequence.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using PEG.Extensions; 4 | 5 | namespace PEG.SyntaxTree 6 | { 7 | public class Sequence : Expression 8 | { 9 | public List Expressions { get; set; } 10 | 11 | public Sequence() 12 | { 13 | Expressions = new List(); 14 | } 15 | 16 | public Sequence(params Expression[] expressions) : this() 17 | { 18 | Expressions.AddRange(expressions); 19 | } 20 | 21 | public static implicit operator Sequence(string sequence) 22 | { 23 | return new Sequence(sequence.ToCharArray().Select(o => (Terminal)o).ToArray()); 24 | } 25 | 26 | public override IEnumerable Execute(ParseEngine engine) 27 | { 28 | var mark = engine.Mark(); 29 | IEnumerable result = NoResults; 30 | 31 | foreach (Expression expression in Expressions) 32 | { 33 | IEnumerable current = expression.Execute(engine); 34 | if (engine.IsFailure(current)) 35 | { 36 | engine.Reset(mark); 37 | return current; 38 | } 39 | result = result.Concat(current); 40 | } 41 | return result; 42 | } 43 | 44 | public override string ToString() 45 | { 46 | return "(" + Expressions.Delimit(" + ") + ")"; 47 | } 48 | 49 | public override void Accept(IExpressionVisitor visitor, T context) 50 | { 51 | visitor.Visit(this, context); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Substitution.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class Substitution : Expression 7 | { 8 | public string Name { get; set; } 9 | public Expression Target { get; set; } 10 | 11 | public override IEnumerable Execute(ParseEngine engine) 12 | { 13 | return Target.Execute(engine); 14 | } 15 | 16 | public override void Accept(IExpressionVisitor visitor, T context) 17 | { 18 | visitor.Visit(this, context); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Terminal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.Cst; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public abstract class Terminal : Expression, ICstTerminalNode 7 | { 8 | Terminal ICstTerminalNode.Terminal 9 | { 10 | get { return this; } 11 | } 12 | 13 | public IEnumerable AsResult(int position) 14 | { 15 | yield return new OutputRecord(this, position); 16 | } 17 | 18 | public abstract string Coalesce(); 19 | 20 | public override void Accept(IExpressionVisitor visitor, T context) 21 | { 22 | visitor.Visit(this, context); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/Token.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PEG.Cst; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class Token : Terminal, ICstNonterminalNode 7 | { 8 | public int AbsoluteIndex { get; set; } 9 | public Nonterminal TokenRule { get; set; } 10 | public IEnumerable Lexeme { get; set; } 11 | public int Position { get; set; } 12 | 13 | private CstNonterminalNode cstNode; 14 | 15 | public Token(Nonterminal tokenRule, IEnumerable lexeme, int absoluteIndex) 16 | { 17 | TokenRule = tokenRule; 18 | Lexeme = lexeme; 19 | AbsoluteIndex = absoluteIndex; 20 | } 21 | 22 | public Nonterminal Nonterminal 23 | { 24 | get { return TokenRule; } 25 | } 26 | 27 | public List Children 28 | { 29 | get { return Cst.Children; } 30 | } 31 | 32 | public override IEnumerable Execute(ParseEngine engine) 33 | { 34 | if (engine.Current is Token && ((Token)engine.Current).TokenRule == TokenRule) 35 | { 36 | if (engine.Consume()) 37 | return AsResult(engine.Position); 38 | else 39 | return null; 40 | } 41 | return null; 42 | } 43 | 44 | public override string Coalesce() 45 | { 46 | return Cst.Coalesce(); 47 | } 48 | 49 | public CstNonterminalNode Cst 50 | { 51 | get 52 | { 53 | if (cstNode == null) 54 | { 55 | cstNode = CstBuilder.Build(Lexeme); 56 | } 57 | return cstNode; 58 | } 59 | } 60 | 61 | public override string ToString() 62 | { 63 | return Coalesce(); 64 | } 65 | 66 | public override void Accept(IExpressionVisitor visitor, T context) 67 | { 68 | visitor.Visit(this, context); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /PEG/SyntaxTree/ZeroOrMore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PEG.SyntaxTree 5 | { 6 | public class ZeroOrMore : Expression 7 | { 8 | public Expression Operand { get; set; } 9 | 10 | public override IEnumerable Execute(ParseEngine engine) 11 | { 12 | IEnumerable result = NoResults; 13 | IEnumerable current; 14 | while (!engine.IsFailure(current = Operand.Execute(engine))) 15 | result = result.Concat(current); 16 | return result; 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return "-" + Operand.ToString(); 22 | } 23 | 24 | public override void Accept(IExpressionVisitor visitor, T context) 25 | { 26 | visitor.Visit(this, context); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /PEG/TokenAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG 4 | { 5 | public class TokenAttribute : Attribute 6 | { 7 | public bool IsOmitted { get; set; } 8 | public bool TokenizeChildren { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /PEG/TokenParseInput.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using PEG.SyntaxTree; 4 | 5 | namespace PEG 6 | { 7 | public class TokenParseInput : IParseInput 8 | { 9 | private Terminal[] input; 10 | 11 | public TokenParseInput(Terminal[] input) 12 | { 13 | this.input = input; 14 | } 15 | 16 | public Terminal this[int index] 17 | { 18 | get { return input[index]; } 19 | } 20 | 21 | public int Length 22 | { 23 | get { return input.Length; } 24 | } 25 | 26 | IEnumerator IEnumerable.GetEnumerator() 27 | { 28 | return GetEnumerator(); 29 | } 30 | 31 | public IEnumerator GetEnumerator() 32 | { 33 | for (int i = 0; i < Length; i++) 34 | yield return this[i]; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /PEG/TokenizerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG 4 | { 5 | public class TokenizerAttribute : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PEG/Utils/BitShift.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG.Utils 4 | { 5 | public static class BitShift 6 | { 7 | public static long ShiftLeft(int shift) 8 | { 9 | return (long)Math.Pow(2, shift); 10 | } 11 | 12 | public static bool ShiftRight(this long value) 13 | { 14 | return Math.Log(value, 2) == 1; 15 | } 16 | 17 | public static readonly ulong[] ByteMasks = new ulong[8]; 18 | 19 | static BitShift() 20 | { 21 | for (int i = 0; i < 8; i++) 22 | { 23 | ulong value = 0; 24 | for (int j = 0; j < 8; j++) 25 | { 26 | if (i != j) 27 | { 28 | value |= (ulong)Byte.MaxValue << (j * 8); 29 | } 30 | } 31 | ByteMasks[i] = value; 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /PEG/Utils/BooleanSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PEG.Utils 4 | { 5 | public class BooleanSet 6 | { 7 | private ulong[] partitions; 8 | // private DynamicArray partitions = new DynamicArray(); 9 | private int count; 10 | private const int partitionSize = 64; 11 | private const int segmentSize = 8; 12 | 13 | private BooleanSet() 14 | { 15 | } 16 | 17 | public BooleanSet(int size) 18 | { 19 | int length = size / partitionSize; 20 | if (size % partitionSize > 0) 21 | length++; 22 | partitions = new ulong[length]; 23 | } 24 | 25 | public bool this[int data] 26 | { 27 | get { return Contains(data); } 28 | set 29 | { 30 | if (value) 31 | Add(data); 32 | else 33 | Remove(data); 34 | } 35 | } 36 | 37 | public void Add(int value) 38 | { 39 | int partitionIndex = value / partitionSize; 40 | int indexInPartition = value % partitionSize; 41 | int segmentIndex = indexInPartition / segmentSize; 42 | int bitIndex = indexInPartition % segmentSize; 43 | 44 | ulong partition = partitions[partitionIndex]; 45 | byte segment = (byte)((partition >> (segmentIndex * segmentSize)) & 0xFF); 46 | 47 | byte mask = (byte)((long)Math.Pow(2, bitIndex)); 48 | bool exists = (segment & mask) != 0; 49 | 50 | if (!exists) 51 | { 52 | segment |= mask; 53 | partition |= (ulong)segment << (segmentIndex * segmentSize); 54 | partitions[partitionIndex] = partition; 55 | count++; 56 | } 57 | } 58 | 59 | public void Remove(int value) 60 | { 61 | int partitionIndex = value / partitionSize; 62 | int indexInPartition = value % partitionSize; 63 | int segmentIndex = indexInPartition / segmentSize; 64 | int bitIndex = indexInPartition % segmentSize; 65 | 66 | ulong partition = partitions[partitionIndex]; 67 | byte segment = (byte)((partition >> (segmentIndex * 8)) & 0xFF); 68 | 69 | byte mask = (byte)((long)Math.Pow(2, bitIndex)); 70 | bool exists = (segment & mask) != 0; 71 | 72 | if (exists) 73 | { 74 | segment &= (byte)~mask; 75 | partition = (BitShift.ByteMasks[segmentIndex] & partition) | ((ulong)segment << (segmentIndex * segmentSize)); 76 | partitions[partitionIndex] = partition; 77 | count--; 78 | } 79 | } 80 | 81 | public bool Contains(int value) 82 | { 83 | int partitionIndex = value / partitionSize; 84 | int indexInPartition = value % partitionSize; 85 | int segmentIndex = indexInPartition / segmentSize; 86 | int bitIndex = indexInPartition % segmentSize; 87 | 88 | ulong partition = partitions[partitionIndex]; 89 | byte segment = (byte)((partition >> (segmentIndex * 8)) & 0xFF); 90 | 91 | byte mask = (byte)((long)Math.Pow(2, bitIndex)); 92 | bool exists = (segment & mask) != 0; 93 | return exists; 94 | } 95 | 96 | public BooleanSet Copy() 97 | { 98 | BooleanSet copy = new BooleanSet(); 99 | copy.partitions = new ulong[partitions.Length]; 100 | partitions.CopyTo(copy.partitions, 0); 101 | return copy; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /PEG/Utils/DictionaryList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace PEG.Utils 6 | { 7 | public class DictionaryList : IEnumerable 8 | { 9 | private Dictionary> dictionary = new Dictionary>(); 10 | 11 | public int GetCount(TKey key) 12 | { 13 | List list = this[key]; 14 | return list != null ? list.Count : 0; 15 | } 16 | 17 | public List this[TKey key] 18 | { 19 | get 20 | { 21 | List list; 22 | if (!dictionary.TryGetValue(key, out list) || list == null) 23 | { 24 | list = new List(); 25 | dictionary[key] = list; 26 | } 27 | return list; 28 | } 29 | set 30 | { 31 | dictionary[key] = value; 32 | } 33 | } 34 | 35 | #region IEnumerable Members 36 | 37 | public IEnumerator GetEnumerator() 38 | { 39 | return dictionary.Keys.GetEnumerator(); 40 | } 41 | 42 | IEnumerator IEnumerable.GetEnumerator() 43 | { 44 | return GetEnumerator(); 45 | } 46 | 47 | #endregion 48 | 49 | public void AddIfUnique(TKey key, TValue value) 50 | { 51 | List list; 52 | if (!dictionary.TryGetValue(key, out list)) 53 | { 54 | list = new List(); 55 | dictionary[key] = list; 56 | } 57 | if (!list.Contains(value)) 58 | list.Add(value); 59 | } 60 | 61 | public void Add(TKey key, TValue value) 62 | { 63 | List list; 64 | if (!dictionary.TryGetValue(key, out list)) 65 | { 66 | list = new List(); 67 | dictionary[key] = list; 68 | } 69 | list.Add(value); 70 | } 71 | 72 | public IEnumerable GetValues(TKey key) 73 | { 74 | List list; 75 | if (dictionary.TryGetValue(key, out list)) 76 | { 77 | return list; 78 | } 79 | else 80 | { 81 | return Enumerable.Empty(); 82 | } 83 | } 84 | 85 | public TValue[] ToArray() 86 | { 87 | var result = new List(); 88 | foreach (var list in dictionary.Values) 89 | foreach (TValue value in list) 90 | result.Add(value); 91 | return result.ToArray(); 92 | } 93 | 94 | public bool ContainsKey(TKey key) 95 | { 96 | return dictionary.ContainsKey(key); 97 | } 98 | 99 | public int GetValuesCount(TKey key) 100 | { 101 | return dictionary.ContainsKey(key) ? dictionary[key].Count : 0; 102 | } 103 | 104 | public IEnumerable Values 105 | { 106 | get 107 | { 108 | foreach (var value in dictionary.Values) 109 | { 110 | foreach (var current in value) 111 | { 112 | yield return current; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /PEG/Utils/DictionarySet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace PEG.Utils 6 | { 7 | public class DictionarySet : IEnumerable 8 | { 9 | private Dictionary> dictionary = new Dictionary>(); 10 | 11 | public int GetCount(TKey key) 12 | { 13 | HashSet list = this[key]; 14 | return list != null ? list.Count : 0; 15 | } 16 | 17 | public HashSet this[TKey key] 18 | { 19 | get 20 | { 21 | HashSet list; 22 | if (!dictionary.TryGetValue(key, out list)) 23 | { 24 | list = new HashSet(); 25 | dictionary[key] = list; 26 | } 27 | return list; 28 | } 29 | } 30 | 31 | #region IEnumerable Members 32 | 33 | public IEnumerator GetEnumerator() 34 | { 35 | return dictionary.Keys.GetEnumerator(); 36 | } 37 | 38 | IEnumerator IEnumerable.GetEnumerator() 39 | { 40 | return GetEnumerator(); 41 | } 42 | 43 | #endregion 44 | 45 | public void Clear() 46 | { 47 | dictionary.Clear(); 48 | } 49 | 50 | public void AddIfUnique(TKey key, TValue value) 51 | { 52 | HashSet list; 53 | if (!dictionary.TryGetValue(key, out list)) 54 | { 55 | list = new HashSet(); 56 | dictionary[key] = list; 57 | } 58 | if (!list.Contains(value)) 59 | list.Add(value); 60 | } 61 | 62 | public void Add(TKey key, TValue value) 63 | { 64 | HashSet list; 65 | if (!dictionary.TryGetValue(key, out list)) 66 | { 67 | list = new HashSet(); 68 | dictionary[key] = list; 69 | } 70 | list.Add(value); 71 | } 72 | 73 | public IEnumerable GetValues(TKey key) 74 | { 75 | HashSet list; 76 | if (dictionary.TryGetValue(key, out list)) 77 | { 78 | return list; 79 | } 80 | else 81 | { 82 | return Enumerable.Empty(); 83 | } 84 | } 85 | 86 | public TValue[] ToArray() 87 | { 88 | var result = new List(); 89 | foreach (var list in dictionary.Values) 90 | foreach (TValue value in list) 91 | result.Add(value); 92 | return result.ToArray(); 93 | } 94 | 95 | public bool ContainsKey(TKey key) 96 | { 97 | return dictionary.ContainsKey(key); 98 | } 99 | 100 | public int GetValuesCount(TKey key) 101 | { 102 | return dictionary.ContainsKey(key) ? dictionary[key].Count : 0; 103 | } 104 | 105 | public IEnumerable Values 106 | { 107 | get 108 | { 109 | foreach (var value in dictionary.Values) 110 | { 111 | foreach (var current in value) 112 | { 113 | yield return current; 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /PEG/Utils/DynamicArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace PEG.Utils 6 | { 7 | public class DynamicArray : IEnumerable 8 | { 9 | private List storage = new List(); 10 | private T defaultValue; 11 | 12 | public DynamicArray() 13 | { 14 | } 15 | 16 | public DynamicArray(T defaultValue) 17 | { 18 | this.defaultValue = defaultValue; 19 | } 20 | 21 | public void SetMaxLength(int length) 22 | { 23 | if (storage.Count > length) 24 | storage.RemoveRange(length, storage.Count - length); 25 | } 26 | 27 | public virtual T this[int index] 28 | { 29 | get 30 | { 31 | if (index >= storage.Count) 32 | { 33 | return defaultValue; 34 | } 35 | else 36 | { 37 | return storage[index]; 38 | } 39 | } 40 | set 41 | { 42 | while (index >= storage.Count) 43 | storage.Add(defaultValue); 44 | storage[index] = value; 45 | } 46 | } 47 | 48 | IEnumerator IEnumerable.GetEnumerator() 49 | { 50 | return GetEnumerator(); 51 | } 52 | 53 | public IEnumerator GetEnumerator() 54 | { 55 | return storage.GetEnumerator(); 56 | } 57 | 58 | public DynamicArray Copy() 59 | { 60 | DynamicArray copy = new DynamicArray(defaultValue); 61 | copy.storage.AddRange(this); 62 | return copy; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.0.{build} 2 | skip_tags: true 3 | image: Visual Studio 2019 Preview 4 | configuration: Release 5 | pull_requests: 6 | do_not_increment_build_number: true 7 | skip_branch_with_pr: true 8 | 9 | environment: 10 | LOGGER: '/l:"C:\Program Files\AppVeyor\BuildAgent\dotnetcore\Appveyor.MSBuildLogger.dll"' 11 | 12 | build_script: 13 | - cmd: dotnet --version 14 | - cmd: dotnet build %LOGGER% -v m -c Release 15 | 16 | test_script: 17 | - cmd: dotnet test -v m -c Release --test-adapter-path:. --logger:Appveyor 18 | 19 | 20 | artifacts: 21 | - path: '**\*.nupkg' 22 | name: nupkg 23 | - path: '**\*.snupkg' 24 | name: snupkg 25 | 26 | 27 | cache: 28 | - '%LocalAppData%\NuGet\Cache' 29 | - '%USERPROFILE%\.nuget\packages' --------------------------------------------------------------------------------