├── .gitattributes ├── .gitignore ├── Boilerplate ├── Boilerplate.csproj ├── Grammars.cs ├── Grammars.ecs ├── Grammars.out.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── CalcExample-Standalone ├── BaseParserForList.cs ├── CalcExample-Standalone.NET40.csproj ├── CalcExample-Standalone.sln ├── Grammars.ecs ├── Grammars.out.cs ├── LesGrammars.les ├── LexerSource.cs ├── ParserSource.cs ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── CalcExample-UsingLoycLibs ├── CalcExample.NET40.csproj ├── Grammars.ecs ├── Grammars.out.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Test.ecs ├── Test.out.cs └── packages.config ├── CalcExample-UsingLoycTrees ├── CalcExample-LoycTrees.NET40.csproj ├── MyGrammars.ecs ├── MyGrammars.out.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── EnhancedC#Parser ├── EcsLanguageService.cs ├── EcsLexer.cs ├── EcsLexerGrammar.les ├── EcsLexerGrammar.out.cs ├── EcsParser.cs ├── EcsParserGrammar.les ├── EcsParserGrammar.out.cs ├── EcsPrecedence.cs ├── EcsPreprocessor.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TokenType.cs ├── TryEcsParser.NET40.csproj └── packages.config ├── JsonExample ├── JsonExample.csproj ├── JsonParser.ecs ├── JsonParser.out.cs ├── JsonPrinter.cs ├── PrinterState.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── License.txt ├── README.md └── Samples.NET40.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize line endings of *.cs (text=auto is supposed to mean "use LFs") 2 | *.cs text=auto eol=lf 3 | # Normalize line endings of *.ecs (eol=lf is supposed to mean "store LFs in working directory") 4 | *.ecs text=auto eol=lf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | packages/ 127 | ## TODO: If the tool you use requires repositories.config uncomment the next line 128 | #!packages/repositories.config 129 | 130 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 131 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 132 | !packages/build/ 133 | 134 | # Windows Azure Build Output 135 | csx/ 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.dbproj.schemaview 150 | *.pfx 151 | *.publishsettings 152 | node_modules/ 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | *.mdf 166 | *.ldf 167 | 168 | # Business Intelligence projects 169 | *.rdl.data 170 | *.bim.layout 171 | *.bim_*.settings 172 | 173 | # Microsoft Fakes 174 | FakesAssemblies/ 175 | 176 | # Visual Studio Code settings 177 | .settings/ 178 | -------------------------------------------------------------------------------- /Boilerplate/Boilerplate.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {AA20B0B4-0773-49C5-AD11-D3C156E4B865} 9 | Exe 10 | Properties 11 | MyLanguage 12 | Boilerplate 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Collections.dll 39 | True 40 | 41 | 42 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Essentials.dll 43 | True 44 | 45 | 46 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Syntax.dll 47 | True 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | True 56 | True 57 | Grammars.ecs 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | LLLPG 66 | Grammars.out.cs 67 | 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /Boilerplate/Grammars.cs: -------------------------------------------------------------------------------- 1 | // Currently, no IntelliSense (code completion) is available in .ecs files, 2 | // so it can be useful to split your Lexer and Parser classes between two 3 | // files. In this file (the .cs file) IntelliSense will be available and 4 | // the other file (the .ecs file) contains your grammar code. 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using Loyc; // optional (for IMessageSink, Symbol, etc.) 10 | using Loyc.Collections; // optional (many handy interfaces & classes) 11 | using Loyc.Syntax.Lexing; // For BaseLexer 12 | using Loyc.Syntax; // For BaseParser and LNode 13 | 14 | namespace MyLanguage 15 | { 16 | // You can define your own `Token` structure, or use the default `Token` in 17 | // the `Loyc.Syntax.Lexing` namespace; in the latter case, we should define 18 | // this extension method on it because the default `Token` just uses a raw 19 | // `int` as the token type. 20 | public static class TokenExt { 21 | public static TokenType Type(this Token t) { return (TokenType)t.TypeInt; } 22 | } 23 | 24 | partial class Lexer : BaseILexer 25 | { 26 | // When using the Loyc libraries, `BaseLexer` and `BaseILexer` read character 27 | // data from an `ICharSource`, which the string wrapper `UString` implements. 28 | public Lexer(string text, string fileName = "") 29 | : this((UString)text, fileName) { } 30 | public Lexer(ICharSource text, string fileName = "") 31 | : base(text, fileName) { } 32 | 33 | private int _startIndex; 34 | 35 | // Creates a Token 36 | private Token T(TokenType type, object value) 37 | { 38 | return new Token((int)type, _startIndex, InputPosition - _startIndex, value); 39 | } 40 | 41 | // Gets the text of the current token that has been parsed so far 42 | private UString Text() 43 | { 44 | return CharSource.Slice(_startIndex, InputPosition - _startIndex); 45 | } 46 | } 47 | 48 | partial class Parser : BaseParserForList 49 | { 50 | public static List Parse(string text, string fileName = "") 51 | { 52 | var lexer = new Lexer(text, fileName); 53 | // Lexer is derived from BaseILexer, which implements IEnumerator. 54 | // Buffered() is an extension method that gathers the output of the 55 | // enumerator into a list so that the parser can consume it. 56 | var parser = new Parser(lexer.Buffered(), lexer.SourceFile); 57 | return parser.Numbers(); 58 | } 59 | 60 | protected Parser(IList list, ISourceFile file, int startIndex = 0) 61 | : base(list, default(Token), file, startIndex) {} 62 | 63 | // Used for error reporting 64 | protected override string ToString(int tokenType) { 65 | return ((TokenType)tokenType).ToString(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Boilerplate/Grammars.ecs: -------------------------------------------------------------------------------- 1 | using System(, .Text, .Linq, .Collections.Generic, .Diagnostics); 2 | using Loyc; // optional (for IMessageSink, Symbol, etc.) 3 | using Loyc.Collections; // optional (many handy interfaces & classes) 4 | using Loyc.Syntax.Lexing; // For BaseLexer 5 | using Loyc.Syntax; // For BaseParser and LNode 6 | 7 | namespace MyLanguage; // In EC#, braces around the rest of the file are optional 8 | 9 | using TT = TokenType; // Abbreviate TokenType as TT 10 | 11 | public enum TokenType 12 | { 13 | EOF = 0, // End-of-file. Conventional to use 0 so that default(Token) is EOF 14 | Newline = 1, 15 | Number = 2, 16 | /* TODO: add more token names here */ 17 | } 18 | 19 | partial class Lexer 20 | { 21 | LLLPG (lexer); // Lexer starts here 22 | 23 | public override rule Maybe NextToken() @{ 24 | (' '|'\t')* // ignore spaces 25 | {_startIndex = InputPosition;} 26 | // this is equivalent to (t:Newline / t:Number / ...) { return t; }: 27 | ( any token in t:token { return t; } // `any token` requires v1.8.0 28 | / EOF { return Maybe.NoValue; } 29 | ) 30 | } 31 | 32 | private new token Token Newline @{ 33 | ('\r' '\n'? | '\n') { 34 | AfterNewline(); // increment the current LineNumber 35 | return T(TT.Newline, WhitespaceTag.Value); 36 | } 37 | }; 38 | private token Token Number() @{ 39 | '0'..'9'+ ('.' '0'..'9'+)? { 40 | var text = Text(); 41 | return T(TT.Number, ParseHelpers.TryParseDouble(ref text, radix: 10)); 42 | } 43 | }; 44 | 45 | // TODO: define more tokens here 46 | } 47 | 48 | partial class Parser 49 | { 50 | LLLPG (parser(matchType: int, laType: TokenType, terminalType: Token)); // Parser starts here 51 | 52 | rule List Numbers @{ 53 | {$result = new List();} 54 | (n:TT.Number {$result.Add((double)n.Value);})* 55 | }; 56 | 57 | // TODO: delete the above rule and define your own instead! 58 | } 59 | -------------------------------------------------------------------------------- /Boilerplate/Grammars.out.cs: -------------------------------------------------------------------------------- 1 | // Generated from Grammars.ecs by LeMP custom tool. LeMP version: 1.8.0.0 2 | // Note: you can give command-line arguments to the tool via 'Custom Tool Namespace': 3 | // --no-out-header Suppress this message 4 | // --verbose Allow verbose messages (shown by VS as 'warnings') 5 | // --timeout=X Abort processing thread after X seconds (default: 10) 6 | // --macros=FileName.dll Load macros from FileName.dll, path relative to this file 7 | // Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG); 8 | using System; 9 | using System.Text; 10 | using System.Linq; 11 | using System.Collections.Generic; 12 | using System.Diagnostics; 13 | using Loyc; 14 | using Loyc.Collections; 15 | using Loyc.Syntax.Lexing; 16 | using Loyc.Syntax; 17 | namespace MyLanguage 18 | { 19 | using TT = TokenType; 20 | public enum TokenType 21 | { 22 | EOF = 0, Newline = 1, Number = 2 23 | } 24 | partial class Lexer 25 | { 26 | public override Maybe NextToken() 27 | { 28 | int la0; 29 | Token t = default(Token); 30 | // Line 24: ([\t ])* 31 | for (;;) { 32 | la0 = LA0; 33 | if (la0 == '\t' || la0 == ' ') 34 | Skip(); 35 | else 36 | break; 37 | } 38 | #line 25 "Grammars.ecs" 39 | _startIndex = InputPosition; 40 | #line default 41 | // Line 27: ( Newline / Number / [\$] ) 42 | la0 = LA0; 43 | if (la0 == '\n' || la0 == '\r') { 44 | t = Newline(); 45 | #line 27 "Grammars.ecs" 46 | return t; 47 | #line default 48 | } else if (la0 >= '0' && la0 <= '9') { 49 | t = Number(); 50 | #line 27 "Grammars.ecs" 51 | return t; 52 | #line default 53 | } else { 54 | Match(-1); 55 | #line 28 "Grammars.ecs" 56 | return Maybe.NoValue; 57 | #line default 58 | } 59 | } 60 | new Token Newline() 61 | { 62 | int la0; 63 | // Line 33: ([\r] ([\n])? | [\n]) 64 | la0 = LA0; 65 | if (la0 == '\r') { 66 | Skip(); 67 | // Line 33: ([\n])? 68 | la0 = LA0; 69 | if (la0 == '\n') 70 | Skip(); 71 | } else 72 | Match('\n'); 73 | #line 34 "Grammars.ecs" 74 | AfterNewline(); 75 | return T(TT.Newline, WhitespaceTag.Value); 76 | #line default 77 | } 78 | Token Number() 79 | { 80 | int la0, la1; 81 | Skip(); 82 | // Line 39: ([0-9])* 83 | for (;;) { 84 | la0 = LA0; 85 | if (la0 >= '0' && la0 <= '9') 86 | Skip(); 87 | else 88 | break; 89 | } 90 | // Line 39: ([.] [0-9] ([0-9])*)? 91 | la0 = LA0; 92 | if (la0 == '.') { 93 | la1 = LA(1); 94 | if (la1 >= '0' && la1 <= '9') { 95 | Skip(); 96 | Skip(); 97 | // Line 39: ([0-9])* 98 | for (;;) { 99 | la0 = LA0; 100 | if (la0 >= '0' && la0 <= '9') 101 | Skip(); 102 | else 103 | break; 104 | } 105 | } 106 | } 107 | #line 40 "Grammars.ecs" 108 | var text = Text(); 109 | return T(TT.Number, ParseHelpers.TryParseDouble(ref text, radix: 10)); 110 | #line default 111 | } 112 | } 113 | partial class Parser 114 | { 115 | List Numbers() 116 | { 117 | TokenType la0; 118 | Token n = default(Token); 119 | List result = default(List); 120 | result = new List(); 121 | // Line 54: (TT.Number)* 122 | for (;;) { 123 | la0 = (TokenType) LA0; 124 | if (la0 == TT.Number) { 125 | n = MatchAny(); 126 | #line 54 "Grammars.ecs" 127 | result.Add((double) n.Value); 128 | #line default 129 | } else 130 | break; 131 | } 132 | return result; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Boilerplate/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc; 6 | 7 | namespace MyLanguage 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | Console.WriteLine("Boilerplate: this example demonstrates the 'boilerplate' - a parser"); 14 | Console.WriteLine("stripped down to the minimum amount of code that any two-stage parser"); 15 | Console.WriteLine("needs (i.e. a parser with a separate lexer). By default, this code"); 16 | Console.WriteLine("simply parses a series of numbers into a List, but you are"); 17 | Console.WriteLine("encouraged to replace this parser with your own."); 18 | Console.WriteLine(); 19 | Console.WriteLine("Awaiting input:"); 20 | string line; 21 | while ((line = Console.ReadLine()).ToLower() != "exit") { 22 | try { 23 | foreach (var result in Parser.Parse(line)) 24 | Console.WriteLine(result); 25 | } catch (LogException ex) { 26 | ex.Msg.WriteTo(MessageSink.Console); 27 | } 28 | Console.WriteLine(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Boilerplate/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Boilerplate")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("Boilerplate")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a6babb86-b022-4cd1-80c4-24c53f9f575c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Boilerplate/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /CalcExample-Standalone/BaseParserForList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Loyc.Syntax 8 | { 9 | /// 10 | /// An base class designed for parsers that use LLLPG (Loyc LL(k) Parser 11 | /// Generator) and receive tokens from any . 12 | /// (Potentially also useful for parsers written by hand.) 13 | /// 14 | /// 15 | /// This is the standalone version that does not require references to 16 | /// Loyc.Essentials.dll, Loyc.Collections.dll and Loyc.Syntax.dll. It was 17 | /// adapted from the original version in Loyc.Syntax.dll. 18 | /// 19 | /// The compiler will ensure that you use this base class correctly. All you 20 | /// have to do is call the base class constructor and override the abstract 21 | /// methods . 22 | /// 23 | /// Data type of complete tokens in the token list. A 24 | /// token contains the type of a "word" in the program (string, identifier, plus 25 | /// sign, etc.), a value (e.g. the name of an identifier), and a range of 26 | /// characters in the source file. See . 27 | /// Note: Token is usually a small struct; this class assumes that it will 28 | /// never be null. 29 | /// A data type, usually int, that represents a 30 | /// token type (identifier, operator, etc.) and implements 31 | /// so it can be compared for equality with other token types; this is also the 32 | /// type of the property. 33 | public abstract class BaseParserForList : BaseParser 34 | where Token : ISimpleToken 35 | where MatchType : IEquatable 36 | { 37 | /// Initializes this object to begin parsing the specified tokens. 38 | /// A list of tokens that the derived class will parse. 39 | /// A token value to return when the input position 40 | /// reaches the end of the token list. Ideally eofToken.StartIndex 41 | /// should contain the position of EOF, but the base class method 42 | /// does not trust this 43 | /// value, and will ensure that the character index returned for EOF is at 44 | /// least as large as the character index of the last token in the file. 45 | /// This means that it is safe to set ISimpleToken{MatchType}.StartIndex 46 | /// to 0 in the EOF token, because when an error message involves EOF, the 47 | /// base class will find a more accurate EOF position. 48 | /// A source file object that will be returned by the 49 | /// The initial index from which to start reading 50 | /// tokens from the list (normally 0). 51 | protected BaseParserForList(IList list, Token eofToken, int startIndex = 0) 52 | : base(startIndex) 53 | { 54 | Reset(list, eofToken, startIndex); 55 | } 56 | 57 | /// Reinitializes the object. This method is called by the constructor. 58 | /// See the constructor for documentation of the parameters. 59 | protected virtual void Reset(IList list, Token eofToken, int startIndex = 0) 60 | { 61 | EofToken = eofToken; 62 | EOF = EofToken.Type; 63 | _tokenList = list; 64 | InputPosition = startIndex; 65 | } 66 | protected void Reset() 67 | { 68 | Reset(TokenList, EofToken); 69 | } 70 | 71 | public MatchType EOF { get; protected set; } 72 | protected Token EofToken; 73 | 74 | /// The IList{Token} that was provided to the constructor, if any. 75 | /// Note: if you are starting to parse a new source file, you should call 76 | /// instead of setting this property. 77 | protected IList TokenList { get { return _tokenList; } } 78 | protected IList _tokenList; 79 | // cached list size to avoid frequently calling the virtual Count property. 80 | // (don't worry, it's updated automatically by LT() if the list size changes) 81 | private int _listCount; 82 | 83 | protected sealed override MatchType EofInt() { return EOF; } 84 | protected sealed override MatchType LA0Int { get { return _lt0.Type; } } 85 | protected sealed override Token LT(int i) 86 | { 87 | i += InputPosition; 88 | if ((uint)i < (uint)_listCount || (uint)i < (uint)(_listCount = _tokenList.Count)) { 89 | try { 90 | return _tokenList[i]; 91 | } catch { 92 | _listCount = _tokenList.Count; 93 | } 94 | } 95 | return EofToken; 96 | } 97 | 98 | protected MatchType LA(int i) { return LT(i).Type; } 99 | protected MatchType LA0 { get { return _lt0.Type; } } 100 | 101 | /// Returns a string representation of the specified token type. 102 | /// These strings are used in error messages. 103 | protected override abstract string ToString(MatchType tokenType); 104 | 105 | protected new int InputPosition 106 | { 107 | [DebuggerStepThrough] 108 | get { return _inputPosition; } 109 | set { 110 | _inputPosition = value; 111 | _lt0 = LT(0); 112 | } 113 | } 114 | 115 | #region Down & Up 116 | // These are used to traverse into token subtrees, e.g. given w=(x+y)*z, 117 | // the outer token list is w=()*z, and the 3 tokens x+y are children of '(' 118 | // So the parser calls something like Down(lparen) to begin parsing inside, 119 | // then it calls Up() to return to the parent tree. 120 | 121 | private Stack, int>> _parents; 122 | 123 | /// Switches to parsing the specified token list at position zero 124 | /// (typically the value of in a token tree 125 | /// produced by .) The original token list and 126 | /// the original are placed on a stack, so you 127 | /// can restore the old list by calling . 128 | /// True if successful, false if children is null. 129 | protected bool Down(IList children) 130 | { 131 | if (children != null) 132 | { 133 | if (_parents == null) 134 | _parents = new Stack, int>>(); 135 | _parents.Push(new KeyValuePair,int>(_tokenList, InputPosition)); 136 | _tokenList = children; 137 | InputPosition = 0; 138 | return true; 139 | } 140 | return false; 141 | } 142 | /// Returns to the old token list saved by . 143 | protected void Up() 144 | { 145 | Debug.Assert(_parents.Count > 0); 146 | var pair = _parents.Pop(); 147 | _tokenList = pair.Key; 148 | InputPosition = pair.Value; 149 | } 150 | /// Calls and returns value. 151 | protected T Up(T value) 152 | { 153 | Up(); 154 | return value; 155 | } 156 | 157 | #endregion 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /CalcExample-Standalone/CalcExample-Standalone.NET40.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {03B6304E-FF66-4468-A7E0-92AA6E543E99} 9 | Exe 10 | Properties 11 | CalcExample 12 | CalcExample 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | True 43 | True 44 | Grammars.ecs 45 | 46 | 47 | Code 48 | 49 | 50 | Code 51 | 52 | 53 | 54 | 55 | 56 | 57 | LLLPG 58 | Grammars.out.cs 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /CalcExample-Standalone/CalcExample-Standalone.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalcExample-Standalone", "CalcExample-Standalone.csproj", "{9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Debug|x86.ActiveCfg = Debug|x86 13 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Debug|x86.Build.0 = Debug|x86 14 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Release|x86.ActiveCfg = Release|x86 15 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /CalcExample-Standalone/Grammars.ecs: -------------------------------------------------------------------------------- 1 | /* 2 | This contains a lexer and parser for a simple calculator. You can combine 3 | the lexer and parser in one file like this, or have separate files for each, 4 | or have 10 parsers in one file, whatever. 5 | 6 | The parser here doesn't create a syntax tree, it just computes the result 7 | directly. 8 | 9 | This file is compiled with the LLLPG Custom Tool. Unfortunately, after 10 | writing the custom tool I found out that Visual Studio does not invoke it 11 | automatically during a build (although it does invoke LLLPG automatically when 12 | you save the file.) Meanwhile, the other obvious way of invoking LLLPG.exe, 13 | using a "pre-build event" command line, doesn't necessarily work correctly 14 | because the "pre-build event" sometimes runs after the C# compiler for some 15 | reason. Is there a third option? Leave a comment on the article: 16 | 17 | http://www.codeproject.com/Articles/664785/A-New-Parser-Generator-for-Csharp 18 | 19 | This is a standalone example, in which the base classes BaseParser and BaseLexer 20 | are in this project. The Loyc library Loyc.Syntax.dll has a slightly different 21 | version of the base classes; to use that version you'd have to add the 22 | following references (which are bundled with LLLPG): 23 | 24 | Loyc.Essentials.dll 25 | Loyc.Collections.dll 26 | Loyc.Syntax.dll 27 | 28 | The documentation at http://loyc.net has more information about these libraries, 29 | and I just started blogging about them: 30 | 31 | http://loyc-etc.blogspot.ca/2014/01/using-loycessentials-introduction.html 32 | 33 | But currently, most of the documentation for Loyc.* DLLs is in the Loyc source 34 | code (SVN repository, e.g. TortoiseSVN), not on the web. 35 | 36 | Comments and newlines are not yet propagated to the output (I'll work on that, 37 | eventually). 38 | 39 | *** This is the "Enhanced C#" version of the code. Aside from possible bugs in 40 | the new EC# parser, it should accept any normal C# code (except LINQ which 41 | is not yet implemented) and the output code should be equivalent but 42 | reformatted. 43 | */ 44 | using System; 45 | using System.Text; 46 | using System.Collections.Generic; 47 | using System.Linq; 48 | using Loyc; 49 | using Loyc.Syntax.Lexing; // for BaseLexer 50 | using Loyc.Syntax; // for BaseParser 51 | 52 | namespace CalcExample 53 | { 54 | using TT = TokenType; // Abbreviate TokenType as TT 55 | 56 | public enum TokenType 57 | { 58 | EOF = -1, // LLLPG lexers assume -1 means EOF (but parsers use "EOF" for EOF) 59 | Space, Id, Num, 60 | Set, Mul, Div, Add, Sub, Exp, 61 | LParen, RParen, Unknown 62 | } 63 | 64 | // Information about each token is bundled into one of these structures 65 | public struct Token : ISimpleToken { 66 | public TokenType Type { get; set; } 67 | public object Value { get; set; } 68 | public int StartIndex { get; set; } 69 | int ISimpleToken.Type { get { return (int)Type; } } 70 | } 71 | 72 | //-------------------------------------------------------------------------- 73 | //-- LEXER ----------------------------------------------------------------- 74 | //-------------------------------------------------------------------------- 75 | 76 | class CalculatorLexer 77 | { 78 | LexerSource _src; 79 | 80 | public CalculatorLexer(string charSource) 81 | { 82 | _src = new LexerSource(charSource); 83 | } 84 | 85 | TokenType _type; 86 | object _value; 87 | int _startIndex; 88 | 89 | LLLPG (lexer(inputSource: _src, inputClass: LexerSource)) 90 | { 91 | private token Id() @[ 92 | ('a'..'z'|'A'..'Z'|'_') 93 | ('a'..'z'|'A'..'Z'|'_'|'0'..'9')* 94 | { _value = _src.CharSource.Substring(_startIndex, _src.InputPosition - _startIndex); } 95 | ]; 96 | private token Num() @[ 97 | {bool dot = false;} 98 | ('.' {dot = true;})? 99 | '0'..'9'+ 100 | (&!{dot} '.' '0'..'9'+)? 101 | { _value = double.Parse(_src.CharSource.Substring(_startIndex, _src.InputPosition - _startIndex)); } 102 | ]; 103 | 104 | public token Token NextToken() 105 | { 106 | _startIndex = _src.InputPosition; 107 | _value = null; 108 | @[ { _type = TT.Num; } Num 109 | | { _type = TT.Id; } Id 110 | | { _type = TT.Exp; } '^' 111 | | { _type = TT.Mul; } '*' 112 | | { _type = TT.Div; } '/' 113 | | { _type = TT.Add; } '+' 114 | | { _type = TT.Sub; } '-' 115 | | { _type = TT.Set; } (":="|"=") 116 | | { _type = TT.Num; } ".nan" { _value = double.NaN; } 117 | | { _type = TT.Num; } ".inf" { _value = double.PositiveInfinity; } 118 | | { _type = TT.LParen; } '(' 119 | | { _type = TT.RParen; } ')' 120 | | { _type = TT.Space; } (' '|'\t') 121 | | error 122 | { _type = TT.EOF; } 123 | ( { _type = TT.Unknown; _src.Error(0, "Unexpected character"); } _)? 124 | ]; 125 | return new Token() { 126 | Type = _type, Value = _value, StartIndex = _startIndex 127 | }; 128 | } 129 | } 130 | } 131 | 132 | //-------------------------------------------------------------------------- 133 | //-- PARSER ---------------------------------------------------------------- 134 | //-------------------------------------------------------------------------- 135 | 136 | public partial class Calculator 137 | { 138 | // Now for the parsing section. Here we have a dictionary of variables 139 | // so users can write "x := 4" and have it saved. The Calculate() method 140 | // is in charge of lexing and parsing; you can see it invokes the Lexer 141 | // and then calls the parser's top-level rule, which is Expr(). 142 | 143 | public ParserSource _src; 144 | 145 | public Dictionary Vars = new Dictionary(); 146 | List _tokens = new List(); 147 | 148 | public double Calculate(string input) 149 | { 150 | // Grab all tokens from the lexer, ignoring spaces 151 | var lexer = new CalculatorLexer(input); 152 | _tokens.Clear(); 153 | Token t; 154 | while (((t = lexer.NextToken()).Type != TT.EOF)) { 155 | if ((t.Type != TT.Space)) 156 | _tokens.Add(t); 157 | } 158 | 159 | _src = new ParserSource(_tokens, new Token { Type=TT.EOF }) { 160 | TokenTypeToString = TokenTypeToString 161 | }; 162 | 163 | // Run the parser 164 | return Expr(); 165 | } 166 | 167 | // Helper method required by ParserSource 168 | string TokenTypeToString(int tokenType) 169 | { 170 | switch ((TT) tokenType) { 171 | case TT.Id: return "identifier"; 172 | case TT.Num: return "number"; 173 | case TT.Set: return "':='"; 174 | case TT.LParen: return "'('"; 175 | case TT.RParen: return "')'"; 176 | default: return ((TokenType) tokenType).ToString(); 177 | } 178 | } 179 | 180 | double Do(double left, Token op, double right) 181 | { 182 | switch (op.Type) { 183 | case TT.Add: return left + right; 184 | case TT.Sub: return left - right; 185 | case TT.Mul: return left * right; 186 | case TT.Div: return left / right; 187 | } 188 | return double.NaN; // unreachable 189 | } 190 | 191 | // Now, here's the parser! This parser doesn't produce a syntax tree like 192 | // most parsers, it simply calculates the result of the input expression 193 | // directly (it's what we call a "traditional" interpreter, as opposed to 194 | // modern interpreters that create a syntax tree and interpret that. 195 | // Modern interpreters avoid the cost of parsing the code repeatedly when 196 | // the code contains loops.) 197 | LLLPG (parser(laType: TokenType, matchType: int, inputSource: _src, inputClass: ParserSource)) 198 | { 199 | // A parser cannot directly match characters. You can, however, use 200 | // aliases like these to pretend that you're matching characters. 201 | // In reality you're still matching tokens produced by the lexer. 202 | // The "alias" command is consumed by LLLPG, and doesn't work outside 203 | // of an LLLPG block. Aliases are replaced inside grammar fragments 204 | // (@[...]) but not inside {actions} or inside &{semantic predicates}. 205 | alias('(' = TT.LParen); 206 | alias(')' = TT.RParen); 207 | alias('^' = TT.Exp); 208 | alias('*' = TT.Mul); 209 | alias('/' = TT.Div); 210 | alias('+' = TT.Add); 211 | alias('-' = TT.Sub); 212 | alias(":=" = TT.Set); 213 | 214 | private rule double Atom() @[ 215 | { double result; } 216 | ( t:=TT.Id { result = Vars[(string) t.Value]; } 217 | | t:=TT.Num { result = (double) t.Value; } 218 | | '(' result=Expr ')' 219 | | error { result = double.NaN; _src.Error(0, "Expected identifer, number, or (parens)"); } 220 | ) 221 | greedy // see footnote below 222 | [ '^' exp:=Atom { result = Math.Pow(result, exp); } ]* 223 | { return result; } 224 | ]; 225 | private rule double Term() @[ 226 | // Supports "mathy" expressions like 3(x-1)(x+1) 227 | result:=Atom 228 | [ rest:=Atom { result *= rest; } ]* 229 | { return result; } 230 | ]; 231 | rule double PrefixExpr() @ 232 | [ '-' r:=Term { return -r; } 233 | | r:=Term { return r; } 234 | ]; 235 | rule double MulExpr() @[ 236 | result:=PrefixExpr 237 | (op:=('*'|'/') rhs:=PrefixExpr { result = Do(result, op, rhs); })* 238 | { return result; } 239 | ]; 240 | rule double AddExpr() @[ 241 | result:=MulExpr 242 | (op:=('+'|'-') rhs:=MulExpr { result = Do(result, op, rhs); })* 243 | { return result; } 244 | ]; 245 | rule double Expr() @[ 246 | { double result; } 247 | ( t:=TT.Id ":=" result=Expr { Vars[t.Value.ToString()] = result; } 248 | | result=AddExpr ) 249 | { return result; } 250 | ]; 251 | 252 | // Footnote about "greedy": As I was about to publish LLLPG 1.0, I added 253 | // the ['^' Atom]* loop to Atom (for exponents like "2^x"), and LLLPG 254 | // reported an "ambiguity": 255 | // 256 | // Warning: ...: Alternatives (1, exit) are ambiguous for input such as 257 | // «TT.Exp TT.Id» (TT.Exp, (TT.Id|TT.Num|TT.LParen)) 258 | // 259 | // In my heart, I feel like this ambiguity doesn't exist ('^' only shows 260 | // up in one place in the whole grammar--how can it be ambiguous?). 261 | // However, this unusual problem seems hard to fix, so I'm not planning 262 | // to fix it; instead I just use "greedy" to suppress the warning 263 | // message. 264 | // 265 | // Let me tell you how LLLPG concludes that the loop is ambiguous. 266 | // Because of the loop ['^' Atom]*, which is located at the end of Atom, 267 | // you can write "Atom ^ Atom ^ Atom", so, clearly, an Atom can be 268 | // followed be followed by "^ Atom". Therefore, Atom's follow set 269 | // includes "^ Atom". So when LLLPG compares alternative 1 (the 270 | // loop body) with the exit condition, it detects that both paths can 271 | // start with "TT.Exp (TT.Id|TT.Num|TT.LParen)", and it concludes that 272 | // alternatives (1, exit) are ambiguous. 273 | // 274 | // What went wrong? Actually, LLLPG's analysis makes a lot of sense, and 275 | // I'm not sure where the analysis is wrong. The loop is in conflict 276 | // with itself, but how could LLLPG detect that and avoid printing a 277 | // warning? 278 | // 279 | // Adding "greedy" was the simplest fix. The warning also disappears 280 | // if Atom is split into two rules (Atom and ExpExpr), like so: 281 | // 282 | // private rule double Atom() @[ 283 | // { double result; } 284 | // ( t:=TT.Id { result = Vars[(string) t.Value]; } 285 | // | t:=TT.Num { result = (double) t.Value; } 286 | // | '(' result=Expr ')' 287 | // | error { result = double.NaN; Error(0, "Expected identifer, number, or (parens)"); } 288 | // ) 289 | // { return result; } 290 | // ]; 291 | // private rule double ExpExpr() @[ 292 | // result:=Atom 293 | // [ '^' exp:=Atom { result = Math.Pow(result, exp); } ]* 294 | // {return result;} 295 | // ]; 296 | // private rule double Term() @[ 297 | // result:=ExpExpr 298 | // [ rest:=ExpExpr { result *= rest; } ]* 299 | // { return result; } 300 | // ]; 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /CalcExample-Standalone/Grammars.out.cs: -------------------------------------------------------------------------------- 1 | // Generated from Grammars.ecs by LeMP custom tool. LeMP version: 1.8.0.0 2 | // Note: you can give command-line arguments to the tool via 'Custom Tool Namespace': 3 | // --no-out-header Suppress this message 4 | // --verbose Allow verbose messages (shown by VS as 'warnings') 5 | // --timeout=X Abort processing thread after X seconds (default: 10) 6 | // --macros=FileName.dll Load macros from FileName.dll, path relative to this file 7 | // Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG); 8 | using System; 9 | using System.Text; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using Loyc; 13 | using Loyc.Syntax.Lexing; 14 | using Loyc.Syntax; 15 | namespace CalcExample 16 | { 17 | using TT = TokenType; 18 | public enum TokenType 19 | { 20 | EOF = -1, Space, Id, Num, Set, Mul, Div, Add, Sub, Exp, LParen, RParen, Unknown 21 | } 22 | public struct Token : ISimpleToken 23 | { 24 | public TokenType Type 25 | { 26 | get; 27 | set; 28 | } 29 | public object Value 30 | { 31 | get; 32 | set; 33 | } 34 | public int StartIndex 35 | { 36 | get; 37 | set; 38 | } 39 | int ISimpleToken.Type 40 | { 41 | get { 42 | return (int) Type; 43 | } 44 | } 45 | } 46 | class CalculatorLexer 47 | { 48 | LexerSource _src; 49 | public CalculatorLexer(string charSource) 50 | { 51 | _src = new LexerSource(charSource); 52 | } 53 | TokenType _type; 54 | object _value; 55 | int _startIndex; 56 | static readonly HashSet Id_set0 = LexerSource.NewSetOfRanges('0', '9', 'A', 'Z', '_', '_', 'a', 'z'); 57 | void Id() 58 | { 59 | int la0; 60 | _src.Skip(); 61 | // Line 93: ([0-9A-Z_a-z])* 62 | for (;;) { 63 | la0 = _src.LA0; 64 | if (Id_set0.Contains(la0)) 65 | _src.Skip(); 66 | else 67 | break; 68 | } 69 | #line 94 "Grammars.ecs" 70 | _value = _src.CharSource.Substring(_startIndex, _src.InputPosition - _startIndex); 71 | #line default 72 | } 73 | void Num() 74 | { 75 | int la0, la1; 76 | #line 97 "Grammars.ecs" 77 | bool dot = false; 78 | #line default 79 | // Line 98: ([.])? 80 | la0 = _src.LA0; 81 | if (la0 == '.') { 82 | _src.Skip(); 83 | #line 98 "Grammars.ecs" 84 | dot = true; 85 | #line default 86 | } 87 | _src.MatchRange('0', '9'); 88 | // Line 99: ([0-9])* 89 | for (;;) { 90 | la0 = _src.LA0; 91 | if (la0 >= '0' && la0 <= '9') 92 | _src.Skip(); 93 | else 94 | break; 95 | } 96 | // Line 100: (&!{dot} [.] [0-9] ([0-9])*)? 97 | la0 = _src.LA0; 98 | if (la0 == '.') { 99 | if (!dot) { 100 | la1 = _src.LA(1); 101 | if (la1 >= '0' && la1 <= '9') { 102 | _src.Skip(); 103 | _src.Skip(); 104 | // Line 100: ([0-9])* 105 | for (;;) { 106 | la0 = _src.LA0; 107 | if (la0 >= '0' && la0 <= '9') 108 | _src.Skip(); 109 | else 110 | break; 111 | } 112 | } 113 | } 114 | } 115 | #line 101 "Grammars.ecs" 116 | _value = double.Parse(_src.CharSource.Substring(_startIndex, _src.InputPosition - _startIndex)); 117 | #line default 118 | } 119 | public Token NextToken() 120 | { 121 | int la0, la1; 122 | _startIndex = _src.InputPosition; 123 | _value = null; 124 | // Line 108: ( Num | Id | [\^] | [*] | [/] | [+] | [\-] | ([:] [=] | [=]) | [.] [n] [a] [n] | [.] [i] [n] [f] | [(] | [)] | [\t ] ) 125 | do { 126 | la0 = _src.LA0; 127 | switch (la0) { 128 | case '.': 129 | { 130 | la1 = _src.LA(1); 131 | if (la1 >= '0' && la1 <= '9') 132 | goto matchNum; 133 | else if (la1 == 'n') { 134 | #line 116 "Grammars.ecs" 135 | _type = TT.Num; 136 | #line default 137 | _src.Skip(); 138 | _src.Skip(); 139 | _src.Match('a'); 140 | _src.Match('n'); 141 | #line 116 "Grammars.ecs" 142 | _value = double.NaN; 143 | #line default 144 | } else if (la1 == 'i') { 145 | #line 117 "Grammars.ecs" 146 | _type = TT.Num; 147 | #line default 148 | _src.Skip(); 149 | _src.Skip(); 150 | _src.Match('n'); 151 | _src.Match('f'); 152 | #line 117 "Grammars.ecs" 153 | _value = double.PositiveInfinity; 154 | #line default 155 | } else 156 | goto error; 157 | } 158 | break; 159 | case '0': 160 | case '1': 161 | case '2': 162 | case '3': 163 | case '4': 164 | case '5': 165 | case '6': 166 | case '7': 167 | case '8': 168 | case '9': 169 | goto matchNum; 170 | case '^': 171 | { 172 | #line 110 "Grammars.ecs" 173 | _type = TT.Exp; 174 | #line default 175 | _src.Skip(); 176 | } 177 | break; 178 | case '*': 179 | { 180 | #line 111 "Grammars.ecs" 181 | _type = TT.Mul; 182 | #line default 183 | _src.Skip(); 184 | } 185 | break; 186 | case '/': 187 | { 188 | #line 112 "Grammars.ecs" 189 | _type = TT.Div; 190 | #line default 191 | _src.Skip(); 192 | } 193 | break; 194 | case '+': 195 | { 196 | #line 113 "Grammars.ecs" 197 | _type = TT.Add; 198 | #line default 199 | _src.Skip(); 200 | } 201 | break; 202 | case '-': 203 | { 204 | #line 114 "Grammars.ecs" 205 | _type = TT.Sub; 206 | #line default 207 | _src.Skip(); 208 | } 209 | break; 210 | case ':': 211 | case '=': 212 | { 213 | #line 115 "Grammars.ecs" 214 | _type = TT.Set; 215 | #line default 216 | // Line 115: ([:] [=] | [=]) 217 | la0 = _src.LA0; 218 | if (la0 == ':') { 219 | _src.Skip(); 220 | _src.Match('='); 221 | } else 222 | _src.Match('='); 223 | } 224 | break; 225 | case '(': 226 | { 227 | #line 118 "Grammars.ecs" 228 | _type = TT.LParen; 229 | #line default 230 | _src.Skip(); 231 | } 232 | break; 233 | case ')': 234 | { 235 | #line 119 "Grammars.ecs" 236 | _type = TT.RParen; 237 | #line default 238 | _src.Skip(); 239 | } 240 | break; 241 | case '\t': 242 | case ' ': 243 | { 244 | #line 120 "Grammars.ecs" 245 | _type = TT.Space; 246 | #line default 247 | _src.Skip(); 248 | } 249 | break; 250 | default: 251 | if (la0 >= 'A' && la0 <= 'Z' || la0 == '_' || la0 >= 'a' && la0 <= 'z') { 252 | #line 109 "Grammars.ecs" 253 | _type = TT.Id; 254 | #line default 255 | Id(); 256 | } else 257 | goto error; 258 | break; 259 | } 260 | break; 261 | matchNum: 262 | { 263 | #line 108 "Grammars.ecs" 264 | _type = TT.Num; 265 | #line default 266 | Num(); 267 | } 268 | break; 269 | error: 270 | { 271 | #line 122 "Grammars.ecs" 272 | _type = TT.EOF; 273 | #line default 274 | // Line 123: ([^\$])? 275 | la0 = _src.LA0; 276 | if (la0 != -1) { 277 | #line 123 "Grammars.ecs" 278 | _type = TT.Unknown; 279 | #line 123 "Grammars.ecs" 280 | _src.Error(0, "Unexpected character"); 281 | #line default 282 | _src.Skip(); 283 | } 284 | } 285 | } while (false); 286 | #line 125 "Grammars.ecs" 287 | return new Token { 288 | Type = _type, Value = _value, StartIndex = _startIndex 289 | }; 290 | #line default 291 | } 292 | } 293 | public partial class Calculator 294 | { 295 | public ParserSource _src; 296 | public Dictionary Vars = new Dictionary(); 297 | List _tokens = new List(); 298 | public double Calculate(string input) 299 | { 300 | var lexer = new CalculatorLexer(input); 301 | _tokens.Clear(); 302 | Token t; 303 | while (((t = lexer.NextToken()).Type != TT.EOF)) { 304 | if ((t.Type != TT.Space)) 305 | _tokens.Add(t); 306 | } 307 | _src = new ParserSource(_tokens, new Token { 308 | Type = TT.EOF 309 | }) { 310 | TokenTypeToString = TokenTypeToString 311 | }; 312 | return Expr(); 313 | } 314 | string TokenTypeToString(int tokenType) 315 | { 316 | switch ((TT) tokenType) { 317 | case TT.Id: 318 | return "identifier"; 319 | case TT.Num: 320 | return "number"; 321 | case TT.Set: 322 | return "':='"; 323 | case TT.LParen: 324 | return "'('"; 325 | case TT.RParen: 326 | return "')'"; 327 | default: 328 | return ((TokenType) tokenType).ToString(); 329 | } 330 | } 331 | double Do(double left, Token op, double right) 332 | { 333 | switch (op.Type) { 334 | case TT.Add: 335 | return left + right; 336 | case TT.Sub: 337 | return left - right; 338 | case TT.Mul: 339 | return left * right; 340 | case TT.Div: 341 | return left / right; 342 | } 343 | return double.NaN; 344 | } 345 | double Atom() 346 | { 347 | TokenType la0, la1; 348 | #line 215 "Grammars.ecs" 349 | double result; 350 | #line default 351 | // Line 216: ( TT.Id | TT.Num | TT.LParen Expr TT.RParen ) 352 | la0 = (TokenType) _src.LA0; 353 | if (la0 == TT.Id) { 354 | var t = _src.MatchAny(); 355 | #line 216 "Grammars.ecs" 356 | result = Vars[(string) t.Value]; 357 | #line default 358 | } else if (la0 == TT.Num) { 359 | var t = _src.MatchAny(); 360 | #line 217 "Grammars.ecs" 361 | result = (double) t.Value; 362 | #line default 363 | } else if (la0 == TT.LParen) { 364 | _src.Skip(); 365 | result = Expr(); 366 | _src.Match((int) TT.RParen); 367 | } else { 368 | #line 219 "Grammars.ecs" 369 | result = double.NaN; 370 | #line 219 "Grammars.ecs" 371 | _src.Error(0, "Expected identifer, number, or (parens)"); 372 | #line default 373 | } 374 | // Line 222: greedy(TT.Exp Atom)* 375 | for (;;) { 376 | la0 = (TokenType) _src.LA0; 377 | if (la0 == TT.Exp) { 378 | la1 = (TokenType) _src.LA(1); 379 | if (la1 == TT.Id || la1 == TT.LParen || la1 == TT.Num) { 380 | _src.Skip(); 381 | var exp = Atom(); 382 | #line 222 "Grammars.ecs" 383 | result = Math.Pow(result, exp); 384 | #line default 385 | } else 386 | break; 387 | } else 388 | break; 389 | } 390 | #line 223 "Grammars.ecs" 391 | return result; 392 | #line default 393 | } 394 | double Term() 395 | { 396 | TokenType la0; 397 | var result = Atom(); 398 | // Line 228: (Atom)* 399 | for (;;) { 400 | la0 = (TokenType) _src.LA0; 401 | if (la0 == TT.Id || la0 == TT.LParen || la0 == TT.Num) { 402 | var rest = Atom(); 403 | #line 228 "Grammars.ecs" 404 | result *= rest; 405 | #line default 406 | } else 407 | break; 408 | } 409 | #line 229 "Grammars.ecs" 410 | return result; 411 | #line default 412 | } 413 | double PrefixExpr() 414 | { 415 | TokenType la0; 416 | // Line 232: (TT.Sub Term | Term) 417 | la0 = (TokenType) _src.LA0; 418 | if (la0 == TT.Sub) { 419 | _src.Skip(); 420 | var r = Term(); 421 | #line 232 "Grammars.ecs" 422 | return -r; 423 | #line default 424 | } else { 425 | var r = Term(); 426 | #line 233 "Grammars.ecs" 427 | return r; 428 | #line default 429 | } 430 | } 431 | double MulExpr() 432 | { 433 | TokenType la0; 434 | var result = PrefixExpr(); 435 | // Line 237: ((TT.Div|TT.Mul) PrefixExpr)* 436 | for (;;) { 437 | la0 = (TokenType) _src.LA0; 438 | if (la0 == TT.Div || la0 == TT.Mul) { 439 | var op = _src.MatchAny(); 440 | var rhs = PrefixExpr(); 441 | #line 237 "Grammars.ecs" 442 | result = Do(result, op, rhs); 443 | #line default 444 | } else 445 | break; 446 | } 447 | #line 238 "Grammars.ecs" 448 | return result; 449 | #line default 450 | } 451 | double AddExpr() 452 | { 453 | TokenType la0; 454 | var result = MulExpr(); 455 | // Line 242: ((TT.Add|TT.Sub) MulExpr)* 456 | for (;;) { 457 | la0 = (TokenType) _src.LA0; 458 | if (la0 == TT.Add || la0 == TT.Sub) { 459 | var op = _src.MatchAny(); 460 | var rhs = MulExpr(); 461 | #line 242 "Grammars.ecs" 462 | result = Do(result, op, rhs); 463 | #line default 464 | } else 465 | break; 466 | } 467 | #line 243 "Grammars.ecs" 468 | return result; 469 | #line default 470 | } 471 | double Expr() 472 | { 473 | TokenType la0, la1; 474 | #line 246 "Grammars.ecs" 475 | double result; 476 | #line default 477 | // Line 247: (TT.Id TT.Set Expr | AddExpr) 478 | la0 = (TokenType) _src.LA0; 479 | if (la0 == TT.Id) { 480 | la1 = (TokenType) _src.LA(1); 481 | if (la1 == TT.Set) { 482 | var t = _src.MatchAny(); 483 | _src.Skip(); 484 | result = Expr(); 485 | #line 247 "Grammars.ecs" 486 | Vars[t.Value.ToString()] = result; 487 | #line default 488 | } else 489 | result = AddExpr(); 490 | } else 491 | result = AddExpr(); 492 | #line 249 "Grammars.ecs" 493 | return result; 494 | #line default 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /CalcExample-Standalone/LesGrammars.les: -------------------------------------------------------------------------------- 1 | /* 2 | This contains a lexer and parser for a simple calculator. You can combine 3 | the lexer and parser in one file like this, or have separate files for each, 4 | or have 10 parsers in one file, whatever. 5 | 6 | The parser here doesn't create a syntax tree, it just computes the result 7 | directly. 8 | 9 | This file is compiled with the LLLPG Custom Tool. Unfortunately, after 10 | writing the custom tool I found out that Visual Studio does not invoke it 11 | automatically during a build (although it does invoke LLLPG automatically when 12 | you save the file.) Meanwhile, the other obvious way of invoking LLLPG.exe, 13 | using a "pre-build event" command line, doesn't necessarily work correctly 14 | because the "pre-build event" sometimes runs after the C# compiler for some 15 | reason. Is there a third option? Leave a comment on the article: 16 | 17 | http://www.codeproject.com/Articles/664785/A-New-Parser-Generator-for-Csharp 18 | 19 | This is a standalone example, in which the base classes BaseParser and BaseLexer 20 | are in this project. The Loyc library Loyc.Syntax.dll has a slightly different 21 | version of the base classes; to use that version you'd have to add the 22 | following references (which are bundled with LLLPG): 23 | 24 | Loyc.Essentials.dll 25 | Loyc.Collections.dll 26 | Loyc.Syntax.dll 27 | 28 | The documentation at http://loyc.net has more information about these libraries, 29 | and I just started blogging about them: 30 | 31 | http://loyc-etc.blogspot.ca/2014/01/using-loycessentials-introduction.html 32 | 33 | But currently, most of the documentation for Loyc.* DLLs is in the Loyc source 34 | code (SVN repository, e.g. TortoiseSVN), not on the web. 35 | 36 | Comments and newlines are not yet propagated to the output (I'll work on that, 37 | eventually). 38 | 39 | *** This is the LES version of the code. LLLPG now also supports "Enhanced C#" 40 | as an input language. LLLPG can choose a parser based on file extension. 41 | */ 42 | import macros LeMP.Prelude.Les; 43 | import System; 44 | import System.Text; 45 | import System.Collections.Generic; 46 | import System.Linq; 47 | import Loyc; 48 | import Loyc.Syntax.Lexing; // for BaseLexer 49 | import Loyc.Syntax; // for BaseParser 50 | 51 | namespace CalcExample 52 | { 53 | using TT = TokenType; // Abbreviate TokenType as TT 54 | 55 | public enum TokenType 56 | { 57 | EOF = -1; // LLLPG lexers assume -1 means EOF (while parsers use "EOF" for EOF) 58 | Space; Id; Num; 59 | Set; Mul; Div; Add; Sub; Exp; 60 | LParen; RParen; Unknown; 61 | }; 62 | 63 | // Information about each token is bundled into one of these structures 64 | public struct Token { 65 | public Type::TokenType; 66 | public Value::object; 67 | public StartIndex::int; 68 | }; 69 | 70 | //-------------------------------------------------------------------------- 71 | //-- LEXER ----------------------------------------------------------------- 72 | //-------------------------------------------------------------------------- 73 | 74 | class CalculatorLexer(BaseLexer!(List!char)) 75 | { 76 | public cons CalculatorLexer(charSource::List!char) 77 | { 78 | base(charSource); 79 | }; 80 | protected override def Error(inputPosition::int, message::string) 81 | { 82 | Console.WriteLine("At index {0}: {1}", inputPosition, message); 83 | }; 84 | 85 | _type::TokenType; 86 | _value::object; 87 | _startIndex::int; 88 | 89 | LLLPG lexer 90 | { 91 | public token NextToken()::Token { 92 | _startIndex = InputPosition; 93 | _value = null; 94 | @[ { _type = TT.Num; } Num 95 | | { _type = TT.Id; } Id 96 | | { _type = TT.Exp; } '^' 97 | | { _type = TT.Mul; } '*' 98 | | { _type = TT.Div; } '/' 99 | | { _type = TT.Add; } '+' 100 | | { _type = TT.Sub; } '-' 101 | | { _type = TT.Set; } ":=" 102 | | { _type = TT.Num; } ".nan" { _value = double.NaN; } 103 | | { _type = TT.Num; } ".inf" { _value = double.PositiveInfinity; } 104 | | { _type = TT.LParen; } '(' 105 | | { _type = TT.RParen; } ')' 106 | | { _type = TT.Space; } (' '|'\t') 107 | | error 108 | { _type = TT.EOF; } (_ { _type = TT.Unknown; })? ]; 109 | return (new Token() { 110 | Type = _type; Value = _value; StartIndex = _startIndex; 111 | }); 112 | }; 113 | 114 | static def Substring(list::List!char, start::int, count::int)::string 115 | { 116 | var sb = (new StringBuilder(count)); 117 | for (i::int = start, i < start + count, i++) 118 | sb.Append(list[i]); 119 | return sb.ToString(); 120 | }; 121 | 122 | private token Id() @[ 123 | ('a'..'z'|'A'..'Z'|'_') 124 | ('a'..'z'|'A'..'Z'|'_'|'0'..'9')* 125 | { _value = Substring(CharSource, _startIndex, InputPosition - _startIndex); } 126 | ]; 127 | private token Num() @[ 128 | {dot::bool = @false;} 129 | ('.' {dot = @true;})? 130 | '0'..'9'+ 131 | (&!{dot} '.' '0'..'9'+)? 132 | { _value = double.Parse(Substring(CharSource, _startIndex, InputPosition - _startIndex)); } 133 | ]; 134 | }; 135 | }; 136 | 137 | //-------------------------------------------------------------------------- 138 | //-- PARSER ---------------------------------------------------------------- 139 | //-------------------------------------------------------------------------- 140 | 141 | public partial class Calculator(BaseParser!Token) 142 | { 143 | // Now for the parsing section. Here we have a dictionary of variables 144 | // so users can write "x := 4" and have it saved. The Calculate() method 145 | // is in charge of lexing and parsing; you can see it invokes the Lexer 146 | // and then calls the parser's top-level rule, which is Expr(). 147 | 148 | public Vars::Dictionary!(string,double) = new(Dictionary!(string,double)()); 149 | _tokens::List!Token = new List!Token(); 150 | _input::string; 151 | 152 | public def Calculate(input::string)::double 153 | { 154 | _input = input; 155 | 156 | // Grab all tokens from the lexer, ignoring spaces 157 | var lexer = (new CalculatorLexer(input.ToList())); 158 | _tokens.Clear(); 159 | t::Token; 160 | while ((t = lexer.NextToken()).Type != EOF) { 161 | if (t.Type != TT.Space) 162 | _tokens.Add(t); 163 | }; 164 | 165 | // Run the parser 166 | InputPosition = 0; // also causes BaseParser to cache LT(0) 167 | return Expr(); 168 | }; 169 | 170 | // These are the helper methods required by BaseParser, followed by 171 | // a couple of things required by LLLPG itself (EOF, LA0, LA(i)). The 172 | // difference between "LA" and "LT" is that "LA" refers to the lookahead 173 | // token type (e.g. TT.Num, TT.Add, etc.), while "LT" refers to the entire 174 | // token (that's the Token structure, in this example.) LLLPG itself only 175 | // requires LA, but BaseParser assumes that there is also a "Token" struct 176 | // or class; this is the type returned by its Match() methods. 177 | 178 | protected override def EofInt()::int { return EOF -> int; }; 179 | protected override prop LA0Int::int { get { return LT0.Type -> int; }; }; 180 | protected override def LT(i::int)::Token 181 | { 182 | i += InputPosition; 183 | if i < _tokens.Count { 184 | return _tokens[i]; 185 | } else { 186 | return (new Token { Type = EOF }); 187 | }; 188 | }; 189 | protected override def Error(lookahead::int, message::string) 190 | { 191 | tIndex::int = InputPosition + lookahead; 192 | cIndex::int = _input.Length; 193 | if (tIndex < _tokens.Count) 194 | cIndex = _tokens[tIndex].StartIndex; 195 | throw (new Exception(string.Format("Error at index {0}: {1}", cIndex, message))); 196 | }; 197 | protected override def ToString(tokenType::int)::string 198 | { 199 | switch tokenType -> TT { 200 | case TT.Id; return "identifier"; 201 | case TT.Num; return "number"; 202 | case TT.Set; return "':='"; 203 | case TT.LParen; return "'('"; 204 | case TT.RParen; return "')'"; 205 | default; return (tokenType->TokenType).ToString(); 206 | }; 207 | }; 208 | 209 | const EOF::TokenType = TT.EOF; 210 | prop LA0::TokenType { get { return LT0.Type; } }; 211 | def LA(offset::int)::TokenType { return LT(offset).Type; }; 212 | 213 | 214 | def Do(left::double, op::Token, right::double)::double 215 | { 216 | switch op.Type { 217 | case TT.Add; return left + right; 218 | case TT.Sub; return left - right; 219 | case TT.Mul; return left * right; 220 | case TT.Div; return left / right; 221 | }; 222 | return double.NaN; // unreachable 223 | }; 224 | 225 | // Now, here's the parser! This parser doesn't produce a syntax tree like 226 | // most parsers, it simply calculates the result of the input expression 227 | // directly (it's what we call a "traditional" interpreter, as opposed to 228 | // modern interpreters that create a syntax tree and interpret that. 229 | // Modern interpreters avoid the cost of parsing the code repeatedly when 230 | // the code contains loops.) 231 | LLLPG parser(laType(TokenType), matchType(int)) 232 | { 233 | // A parser cannot directly match characters. You can, however, use 234 | // aliases like these to pretend that you're matching characters. 235 | // In reality you're still matching tokens produced by the lexer. 236 | // The "alias" command is consumed by LLLPG, and doesn't work outside 237 | // of an LLLPG block. Aliases are replaced inside grammar fragments 238 | // (@[...]) but not inside {actions} or inside &{semantic predicates}. 239 | alias('(' = TT.LParen); 240 | alias(')' = TT.RParen); 241 | alias('^' = TT.Exp); 242 | alias('*' = TT.Mul); 243 | alias('/' = TT.Div); 244 | alias('+' = TT.Add); 245 | alias('-' = TT.Sub); 246 | alias(":=" = TT.Set); 247 | 248 | private rule Atom()::double @[ 249 | { result::double; } 250 | ( t:=TT.Id { result = Vars[t.Value -> string]; } 251 | | t:=TT.Num { result = t.Value -> double; } 252 | | '(' result=Expr ')' 253 | | error { result = double.NaN; Error(InputPosition, "Expected identifer, number, or (parens)"); } 254 | ) 255 | greedy // see footnote below 256 | [ '^' exp:=Atom { result = Math.Pow(result, exp); } ]* 257 | { return result; } 258 | ]; 259 | private rule Term()::double @[ 260 | // Supports "mathy" expressions like 3(x-1)(x+1) 261 | result:=Atom 262 | [ rest:=Atom { result *= rest; } ]* 263 | { return result; } 264 | ]; 265 | rule PrefixExpr()::double @ 266 | [ '-' r:=Term { return (-r); } 267 | | r:=Term { return r; } 268 | ]; 269 | rule MulExpr()::double @[ 270 | result:=PrefixExpr 271 | (op:=('*'|'/') rhs:=PrefixExpr { result = Do(result, op, rhs); })* 272 | { return result; } 273 | ]; 274 | rule AddExpr()::double @[ 275 | result:=MulExpr 276 | (op:=('+'|'-') rhs:=MulExpr { result = Do(result, op, rhs); })* 277 | { return result; } 278 | ]; 279 | rule Expr()::double @[ 280 | { result::double; } 281 | ( t:=TT.Id ":=" result=Expr { Vars[t.Value.ToString()] = result; } 282 | | result=AddExpr ) 283 | { return result; } 284 | ]; 285 | 286 | // Footnote about "greedy": As I was about to publish LLLPG 1.0, I added 287 | // the ['^' Atom]* loop to Atom (for exponents like "2^x"), and LLLPG 288 | // reported an "ambiguity": 289 | // 290 | // Warning: ...: Alternatives (1, exit) are ambiguous for input such as 291 | // «TT.Exp TT.Id» (TT.Exp, (TT.Id|TT.Num|TT.LParen)) 292 | // 293 | // In my heart, I feel like this ambiguity doesn't exist ('^' only shows 294 | // up in one place in the whole grammar--how can it be ambiguous?). 295 | // However, this unusual problem seems hard to fix, so I'm not planning 296 | // to fix it; instead I just use "greedy" to suppress the warning 297 | // message. 298 | // 299 | // Let me tell you how LLLPG concludes that the loop is ambiguous. 300 | // Because of the loop ['^' Atom]*, which is located at the end of Atom, 301 | // you can write "Atom ^ Atom ^ Atom", so, clearly, an Atom can be 302 | // followed be followed by "^ Atom". Therefore, Atom's follow set 303 | // includes "^ Atom". So when LLLPG compares alternative 1 (the 304 | // loop body) with the exit condition, it detects that both paths can 305 | // start with "TT.Exp (TT.Id|TT.Num|TT.LParen)", and it concludes that 306 | // alternatives (1, exit) are ambiguous. 307 | // 308 | // What went wrong? Actually, LLLPG's analysis makes a lot of sense, and 309 | // I'm not sure where the analysis is wrong. The loop is in conflict 310 | // with itself, but how could LLLPG detect that and avoid printing a 311 | // warning? 312 | // 313 | // Adding "greedy" was the simplest fix. The warning also disappears 314 | // if Atom is split into two rules (Atom and ExpExpr), like so: 315 | // 316 | // private rule double Atom() @[ 317 | // { double result; } 318 | // ( t:=TT.Id { result = Vars[(string) t.Value]; } 319 | // | t:=TT.Num { result = (double) t.Value; } 320 | // | '(' result=Expr ')' 321 | // | error { result = double.NaN; Error(InputPosition, "Expected identifer, number, or (parens)"); } 322 | // ) 323 | // { return result; } 324 | // ]; 325 | // private rule double ExpExpr() @[ 326 | // result:=Atom 327 | // [ '^' exp:=Atom { result = Math.Pow(result, exp); } ]* 328 | // {return result;} 329 | // ]; 330 | // private rule double Term() @[ 331 | // result:=ExpExpr 332 | // [ rest:=ExpExpr { result *= rest; } ]* 333 | // { return result; } 334 | // ]; 335 | }; 336 | }; 337 | }; 338 | -------------------------------------------------------------------------------- /CalcExample-Standalone/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CalcExample 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | Console.WriteLine("\n" + 13 | "Welcome to the LLLPG simple calculator demo.\n" + 14 | "Please type an expression like (5 + 10) * 2 or «exit» to quit.\n" + 15 | "Mathy-looking expressions like 2(x-1)x^2 are also accepted.\n" + 16 | "This calculator has only six built-in operators: + - * / ^ :=\n" + 17 | "The := operator lets you save a result in a variable: pi := 3.14159265\n" + 18 | "\"Exercise for the reader\": try adding additional operators!\n"); 19 | 20 | var calc = new Calculator(); 21 | calc.Vars["pi"] = Math.PI; 22 | calc.Vars["e"] = Math.E; 23 | 24 | string line; 25 | while ((line = Console.ReadLine()).ToLower() != "exit") { 26 | try { 27 | Console.WriteLine("== " + calc.Calculate(line)); 28 | } catch (Exception ex) { 29 | Console.WriteLine(ex.Message); 30 | } 31 | Console.WriteLine(); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CalcExample-Standalone/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CalcExample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CalcExample")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b5da2d90-b3e7-4b10-a486-e6e8c5a26441")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/CalcExample.NET40.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988} 9 | Exe 10 | Properties 11 | CalcExample 12 | CalcExample 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Collections.dll 39 | True 40 | 41 | 42 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Essentials.dll 43 | True 44 | 45 | 46 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Syntax.dll 47 | True 48 | 49 | 50 | 51 | 52 | 53 | 54 | True 55 | True 56 | Grammars.ecs 57 | 58 | 59 | 60 | 61 | True 62 | True 63 | Test.ecs 64 | 65 | 66 | 67 | 68 | LLLPG 69 | Grammars.out.cs 70 | 71 | 72 | 73 | LeMP 74 | Test.out.cs 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/Grammars.out.cs: -------------------------------------------------------------------------------- 1 | // Generated from Grammars.ecs by LeMP custom tool. LeMP version: 1.8.0.0 2 | // Note: you can give command-line arguments to the tool via 'Custom Tool Namespace': 3 | // --no-out-header Suppress this message 4 | // --verbose Allow verbose messages (shown by VS as 'warnings') 5 | // --timeout=X Abort processing thread after X seconds (default: 10) 6 | // --macros=FileName.dll Load macros from FileName.dll, path relative to this file 7 | // Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG); 8 | using System; 9 | using System.Text; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using Loyc; 13 | using Loyc.Collections; 14 | using Loyc.Syntax.Lexing; 15 | using Loyc.Syntax; 16 | namespace CalcExample 17 | { 18 | using TT = TokenType; 19 | public enum TokenType 20 | { 21 | EOF = 0, Id, Num, Shr, Shl, Assign, GT, LT, Exp, Mul, Div, Add, Sub, Semicolon, LParen, RParen, Unknown 22 | } 23 | public struct Token : ISimpleToken 24 | { 25 | public TokenType Type 26 | { 27 | get; 28 | set; 29 | } 30 | public object Value 31 | { 32 | get; 33 | set; 34 | } 35 | public int StartIndex 36 | { 37 | get; 38 | set; 39 | } 40 | int ISimpleToken.Type 41 | { 42 | get { 43 | return (int) Type; 44 | } 45 | } 46 | } 47 | partial class CalculatorLexer : IEnumerator 48 | { 49 | public LexerSource Src 50 | { 51 | get; 52 | set; 53 | } 54 | public CalculatorLexer(string text, string fileName = "") 55 | { 56 | Src = (LexerSource) text; 57 | } 58 | public CalculatorLexer(ICharSource text, string fileName = "") 59 | { 60 | Src = new LexerSource(text); 61 | } 62 | Token _tok; 63 | public Token Current 64 | { 65 | get { 66 | return _tok; 67 | } 68 | } 69 | object System.Collections.IEnumerator.Current 70 | { 71 | get { 72 | return Current; 73 | } 74 | } 75 | void System.Collections.IEnumerator.Reset() 76 | { 77 | Src.Reset(); 78 | } 79 | void IDisposable.Dispose() 80 | { 81 | } 82 | public bool MoveNext() 83 | { 84 | int la0, la1; 85 | // Line 161: ([\t ])* 86 | for (;;) { 87 | la0 = Src.LA0; 88 | if (la0 == '\t' || la0 == ' ') 89 | Src.Skip(); 90 | else 91 | break; 92 | } 93 | _tok.StartIndex = Src.InputPosition; 94 | _tok.Value = null; 95 | // Line 164: ( (Num | Id | [.] [n] [a] [n] | [.] [i] [n] [f]) | ([>] [>] / [<] [<] / [=] / [>] / [<] / [\^] / [*] / [/] / [+] / [\-] / [;] / [(] / [)]) ) 96 | do { 97 | la0 = Src.LA0; 98 | switch (la0) { 99 | case '.': 100 | { 101 | la1 = Src.LA(1); 102 | if (la1 >= '0' && la1 <= '9') 103 | goto matchNum; 104 | else if (la1 == 'n') { 105 | #line 166 "Grammars.ecs" 106 | _tok.Type = TT.Num; 107 | #line default 108 | Src.Skip(); 109 | Src.Skip(); 110 | Src.Match('a'); 111 | Src.Match('n'); 112 | #line 166 "Grammars.ecs" 113 | _tok.Value = double.NaN; 114 | #line default 115 | } else if (la1 == 'i') { 116 | #line 167 "Grammars.ecs" 117 | _tok.Type = TT.Num; 118 | #line default 119 | Src.Skip(); 120 | Src.Skip(); 121 | Src.Match('n'); 122 | Src.Match('f'); 123 | #line 167 "Grammars.ecs" 124 | _tok.Value = double.PositiveInfinity; 125 | #line default 126 | } else 127 | goto error; 128 | } 129 | break; 130 | case '0': 131 | case '1': 132 | case '2': 133 | case '3': 134 | case '4': 135 | case '5': 136 | case '6': 137 | case '7': 138 | case '8': 139 | case '9': 140 | goto matchNum; 141 | case '>': 142 | { 143 | la1 = Src.LA(1); 144 | if (la1 == '>') { 145 | Src.Skip(); 146 | Src.Skip(); 147 | #line 198 "Grammars.ecs" 148 | _tok.Type = TT.Shr; 149 | #line default 150 | } else { 151 | Src.Skip(); 152 | #line 198 "Grammars.ecs" 153 | _tok.Type = TT.GT; 154 | #line default 155 | } 156 | } 157 | break; 158 | case '<': 159 | { 160 | la1 = Src.LA(1); 161 | if (la1 == '<') { 162 | Src.Skip(); 163 | Src.Skip(); 164 | #line 198 "Grammars.ecs" 165 | _tok.Type = TT.Shl; 166 | #line default 167 | } else { 168 | Src.Skip(); 169 | #line 198 "Grammars.ecs" 170 | _tok.Type = TT.LT; 171 | #line default 172 | } 173 | } 174 | break; 175 | case '=': 176 | { 177 | Src.Skip(); 178 | #line 198 "Grammars.ecs" 179 | _tok.Type = TT.Assign; 180 | #line default 181 | } 182 | break; 183 | case '^': 184 | { 185 | Src.Skip(); 186 | #line 198 "Grammars.ecs" 187 | _tok.Type = TT.Exp; 188 | #line default 189 | } 190 | break; 191 | case '*': 192 | { 193 | Src.Skip(); 194 | #line 198 "Grammars.ecs" 195 | _tok.Type = TT.Mul; 196 | #line default 197 | } 198 | break; 199 | case '/': 200 | { 201 | Src.Skip(); 202 | #line 198 "Grammars.ecs" 203 | _tok.Type = TT.Div; 204 | #line default 205 | } 206 | break; 207 | case '+': 208 | { 209 | Src.Skip(); 210 | #line 198 "Grammars.ecs" 211 | _tok.Type = TT.Add; 212 | #line default 213 | } 214 | break; 215 | case '-': 216 | { 217 | Src.Skip(); 218 | #line 198 "Grammars.ecs" 219 | _tok.Type = TT.Sub; 220 | #line default 221 | } 222 | break; 223 | case ';': 224 | { 225 | Src.Skip(); 226 | #line 198 "Grammars.ecs" 227 | _tok.Type = TT.Semicolon; 228 | #line default 229 | } 230 | break; 231 | case '(': 232 | { 233 | Src.Skip(); 234 | #line 198 "Grammars.ecs" 235 | _tok.Type = TT.LParen; 236 | #line default 237 | } 238 | break; 239 | case ')': 240 | { 241 | Src.Skip(); 242 | #line 198 "Grammars.ecs" 243 | _tok.Type = TT.RParen; 244 | #line default 245 | } 246 | break; 247 | default: 248 | if (la0 >= 'A' && la0 <= 'Z' || la0 == '_' || la0 >= 'a' && la0 <= 'z') { 249 | #line 165 "Grammars.ecs" 250 | _tok.Type = TT.Id; 251 | #line default 252 | Id(); 253 | } else 254 | goto error; 255 | break; 256 | } 257 | break; 258 | matchNum: 259 | { 260 | #line 164 "Grammars.ecs" 261 | _tok.Type = TT.Num; 262 | #line default 263 | Num(); 264 | } 265 | break; 266 | error: 267 | { 268 | #line 170 "Grammars.ecs" 269 | _tok.Type = TT.EOF; 270 | #line default 271 | // Line 170: ([^\$])? 272 | la0 = Src.LA0; 273 | if (la0 != -1) { 274 | Src.Skip(); 275 | #line 170 "Grammars.ecs" 276 | _tok.Type = TT.Unknown; 277 | #line default 278 | } 279 | } 280 | } while (false); 281 | #line 172 "Grammars.ecs" 282 | return _tok.Type != TT.EOF; 283 | #line default 284 | } 285 | static readonly HashSet Id_set0 = LexerSource.NewSetOfRanges('0', '9', 'A', 'Z', '_', '_', 'a', 'z'); 286 | void Id() 287 | { 288 | int la0; 289 | Src.Skip(); 290 | // Line 177: ([0-9A-Z_a-z])* 291 | for (;;) { 292 | la0 = Src.LA0; 293 | if (Id_set0.Contains(la0)) 294 | Src.Skip(); 295 | else 296 | break; 297 | } 298 | #line 178 "Grammars.ecs" 299 | _tok.Value = Src.CharSource.Slice(_tok.StartIndex, Src.InputPosition - _tok.StartIndex).ToString(); 300 | #line default 301 | } 302 | void Num() 303 | { 304 | int la0, la1; 305 | int dot = 0; 306 | // Line 181: ([.])? 307 | la0 = Src.LA0; 308 | if (la0 == '.') 309 | dot = Src.MatchAny(); 310 | Src.MatchRange('0', '9'); 311 | // Line 182: ([0-9])* 312 | for (;;) { 313 | la0 = Src.LA0; 314 | if (la0 >= '0' && la0 <= '9') 315 | Src.Skip(); 316 | else 317 | break; 318 | } 319 | // Line 183: (&{dot == 0} [.] [0-9] ([0-9])*)? 320 | la0 = Src.LA0; 321 | if (la0 == '.') { 322 | if (dot == 0) { 323 | la1 = Src.LA(1); 324 | if (la1 >= '0' && la1 <= '9') { 325 | Src.Skip(); 326 | Src.Skip(); 327 | // Line 183: ([0-9])* 328 | for (;;) { 329 | la0 = Src.LA0; 330 | if (la0 >= '0' && la0 <= '9') 331 | Src.Skip(); 332 | else 333 | break; 334 | } 335 | } 336 | } 337 | } 338 | #line 184 "Grammars.ecs" 339 | _tok.Value = double.Parse(Src.CharSource.Slice(_tok.StartIndex, Src.InputPosition - _tok.StartIndex).ToString()); 340 | #line default 341 | } 342 | } 343 | public partial class Calculator 344 | { 345 | public Dictionary Vars = new Dictionary(); 346 | public ParserSource Src 347 | { 348 | get; 349 | set; 350 | } 351 | public double Calculate(string input) 352 | { 353 | Token EofToken = new Token { 354 | Type = TT.EOF 355 | }; 356 | var lexer = new CalculatorLexer(input); 357 | Src = new ParserSource(lexer, EofToken, lexer.Src.SourceFile) { 358 | TokenTypeToString = tt => ((TT) tt).ToString() 359 | }; 360 | var result = ExprSequence(); 361 | Src.Match((int) TT.EOF); 362 | return result; 363 | } 364 | static double Do(double left, Token op, double right) 365 | { 366 | switch (op.Type) { 367 | case TT.Add: 368 | return left + right; 369 | case TT.Sub: 370 | return left - right; 371 | case TT.Mul: 372 | return left * right; 373 | case TT.Div: 374 | return left / right; 375 | case TT.Semicolon: 376 | return right; 377 | } 378 | return double.NaN; 379 | } 380 | double Atom() 381 | { 382 | TokenType la0, la1; 383 | double got_Atom = default(double); 384 | double result = default(double); 385 | // Line 263: ( TT.Id | TT.Num | TT.LParen ExprSequence TT.RParen ) 386 | la0 = (TokenType) Src.LA0; 387 | if (la0 == TT.Id) { 388 | var t = Src.MatchAny(); 389 | #line 263 "Grammars.ecs" 390 | result = Vars[(string) t.Value]; 391 | #line default 392 | } else if (la0 == TT.Num) { 393 | var t = Src.MatchAny(); 394 | #line 264 "Grammars.ecs" 395 | result = (double) t.Value; 396 | #line default 397 | } else if (la0 == TT.LParen) { 398 | Src.Skip(); 399 | result = ExprSequence(); 400 | Src.Match((int) TT.RParen); 401 | } else { 402 | #line 266 "Grammars.ecs" 403 | result = double.NaN; 404 | #line 266 "Grammars.ecs" 405 | Src.Error(0, "Expected identifer, number, or (parens)"); 406 | #line default 407 | } 408 | // Line 269: greedy(TT.Exp Atom)* 409 | for (;;) { 410 | la0 = (TokenType) Src.LA0; 411 | if (la0 == TT.Exp) { 412 | la1 = (TokenType) Src.LA(1); 413 | if (la1 == TT.Id || la1 == TT.LParen || la1 == TT.Num) { 414 | Src.Skip(); 415 | got_Atom = Atom(); 416 | #line 269 "Grammars.ecs" 417 | result = Math.Pow(result, got_Atom); 418 | #line default 419 | } else 420 | break; 421 | } else 422 | break; 423 | } 424 | return result; 425 | } 426 | double Term() 427 | { 428 | TokenType la0; 429 | var result = Atom(); 430 | // Line 274: (Atom)* 431 | for (;;) { 432 | la0 = (TokenType) Src.LA0; 433 | if (la0 == TT.Id || la0 == TT.LParen || la0 == TT.Num) { 434 | var rest = Atom(); 435 | #line 274 "Grammars.ecs" 436 | result *= rest; 437 | #line default 438 | } else 439 | break; 440 | } 441 | #line 275 "Grammars.ecs" 442 | return result; 443 | #line default 444 | } 445 | double PrefixExpr() 446 | { 447 | TokenType la0; 448 | // Line 278: (TT.Sub Term | Term) 449 | la0 = (TokenType) Src.LA0; 450 | if (la0 == TT.Sub) { 451 | Src.Skip(); 452 | var r = Term(); 453 | #line 278 "Grammars.ecs" 454 | return -r; 455 | #line default 456 | } else { 457 | var r = Term(); 458 | #line 279 "Grammars.ecs" 459 | return r; 460 | #line default 461 | } 462 | } 463 | double MulExpr() 464 | { 465 | TokenType la0; 466 | var result = PrefixExpr(); 467 | // Line 283: ((TT.Div|TT.Mul) PrefixExpr)* 468 | for (;;) { 469 | la0 = (TokenType) Src.LA0; 470 | if (la0 == TT.Div || la0 == TT.Mul) { 471 | var op = Src.MatchAny(); 472 | var rhs = PrefixExpr(); 473 | #line 283 "Grammars.ecs" 474 | result = Do(result, op, rhs); 475 | #line default 476 | } else 477 | break; 478 | } 479 | #line 284 "Grammars.ecs" 480 | return result; 481 | #line default 482 | } 483 | double AddExpr() 484 | { 485 | TokenType la0; 486 | var result = MulExpr(); 487 | // Line 288: ((TT.Add|TT.Sub) MulExpr)* 488 | for (;;) { 489 | la0 = (TokenType) Src.LA0; 490 | if (la0 == TT.Add || la0 == TT.Sub) { 491 | var op = Src.MatchAny(); 492 | var rhs = MulExpr(); 493 | #line 288 "Grammars.ecs" 494 | result = Do(result, op, rhs); 495 | #line default 496 | } else 497 | break; 498 | } 499 | #line 289 "Grammars.ecs" 500 | return result; 501 | #line default 502 | } 503 | double AssignExpr() 504 | { 505 | TokenType la0, la1; 506 | double result = default(double); 507 | // Line 293: (TT.Id TT.Assign AssignExpr | AddExpr) 508 | la0 = (TokenType) Src.LA0; 509 | if (la0 == TT.Id) { 510 | la1 = (TokenType) Src.LA(1); 511 | if (la1 == TT.Assign) { 512 | var t = Src.MatchAny(); 513 | Src.Skip(); 514 | result = AssignExpr(); 515 | #line 293 "Grammars.ecs" 516 | Vars[t.Value.ToString()] = result; 517 | #line default 518 | } else 519 | result = AddExpr(); 520 | } else 521 | result = AddExpr(); 522 | return result; 523 | } 524 | double ExprSequence() 525 | { 526 | TokenType la0; 527 | double result = default(double); 528 | result = AssignExpr(); 529 | // Line 297: (TT.Semicolon AssignExpr)* 530 | for (;;) { 531 | la0 = (TokenType) Src.LA0; 532 | if (la0 == TT.Semicolon) { 533 | Src.Skip(); 534 | result = AssignExpr(); 535 | } else 536 | break; 537 | } 538 | return result; 539 | } 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc.Syntax; 6 | using CalcExample; 7 | using Loyc.Collections; 8 | 9 | namespace CalcExample 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | Console.WriteLine(); 16 | Console.WriteLine( 17 | "Welcome to the LLLPG simple calculator demo.\n" + 18 | "Please type an expression like (5 + 10) * 2 or «exit» to quit.\n" + 19 | "Mathy-looking expressions like 2(x-1)x^2 are also accepted.\n" + 20 | "This calculator has only six built-in operators: + - * / ^ =\n" + 21 | "The = operator lets you save a result in a variable: pi = 3.14159265\n" + 22 | "\"Exercise for the reader\": try adding additional operators!\n" + 23 | "Error handling could also be improved.\n"); 24 | 25 | var calc = new Calculator(); 26 | calc.Vars["pi"] = Math.PI; 27 | calc.Vars["e"] = Math.E; 28 | 29 | string line; 30 | while ((line = Console.ReadLine()).ToLower() != "exit") { 31 | try { 32 | Console.WriteLine("== " + calc.Calculate(line)); 33 | } catch (Exception ex) { 34 | Console.WriteLine(ex.Message); 35 | } 36 | Console.WriteLine(); 37 | } 38 | } 39 | } 40 | 41 | partial class CalculatorLexer 42 | { 43 | void IntelliSense() { 44 | // Tip: sadly, IntelliSense doesn't work inside your *.ecs file. But 45 | // as a workaround, you can make your class partial and then put part 46 | // of it in a normal C# file, just to get access to IntelliSense and 47 | // a better debugging experience. 48 | // e.g. type "Source." here to see what's inside LexerSource... 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CalcExample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CalcExample")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b5da2d90-b3e7-4b10-a486-e6e8c5a26441")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/Test.ecs: -------------------------------------------------------------------------------- 1 | /* 2 | A scratchpad for playing 3 | */ 4 | #importMacros(Loyc.LLPG); 5 | using System; 6 | using System.Linq; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using Loyc; 10 | using Loyc.Syntax; 11 | using Loyc.Syntax.Lexing; 12 | 13 | struct StringToken : ISimpleToken 14 | { 15 | public string Type { get; set; } 16 | public object Value { get { return Type; } } 17 | public int StartIndex { get; set; } 18 | } 19 | 20 | class ExprParser : BaseParserForList 21 | { 22 | public ExprParser(string input) 23 | : this(input.Split(' ').Select(word => 24 | new StringToken { Type=word }).ToList()) {} 25 | public ExprParser(IList tokens, ISourceFile file = null) 26 | : base(tokens, default(StringToken), file ?? EmptySourceFile.Unknown) 27 | { F = new LNodeFactory(SourceFile); } 28 | 29 | protected override string ToString(string tokenType) { return tokenType; } 30 | 31 | LNodeFactory F = new LNodeFactory(EmptySourceFile.Unknown); 32 | LNode Op(LNode lhs, StringToken op, LNode rhs) { 33 | return F.Call((Symbol)op.Type, lhs, rhs, lhs.Range.StartIndex, rhs.Range.EndIndex); 34 | } 35 | 36 | [AddCsLineDirectives(false)] 37 | LLLPG(parser(laType: string, terminalType: StringToken)); 38 | 39 | public rule LNode Expr(int prec = 0) @[ 40 | ( "-" r:=Expr(50) { $result = F.Call((Symbol)"-", r, 41 | $"-".StartIndex, r.Range.EndIndex); } 42 | / result:Atom ) 43 | greedy // to suppress ambiguity warning 44 | ( // Remember to add [Local] when your predicate uses a local variable 45 | &{[Local] prec <= 10} 46 | '''=''' r:=Expr(10) 47 | { $result = Op($result, $'''=''', r); } 48 | | &{[Local] prec < 20} 49 | op:=('''&&'''|'''||''') r:=Expr(20) 50 | { $result = Op($result, op, r); } 51 | | &{[Local] prec < 30} 52 | op:=('''>'''|'''<'''|'''>='''|'''<='''|'''=='''|'''!=''') r:=Expr(30) 53 | { $result = Op($result, op, r); } 54 | | &{[Local] prec < 40} 55 | op:=('''+'''|'''-''') r:=Expr(40) 56 | { $result = Op($result, op, r); } 57 | | &{[Local] prec < 50} 58 | op:=('''*'''|'''/'''|'''>>'''|'''<<''') r:=Expr(50) 59 | { $result = Op($result, op, r); } 60 | | '''(''' Expr ''')''' 61 | { $result = F.Call($result, $Expr, $result.Range.StartIndex); } 62 | | '''.''' rhs:Atom 63 | { $result = F.Dot ($result, $rhs, $result.Range.StartIndex); } 64 | )* 65 | ]; 66 | rule LNode PrefixExpr() @[ 67 | ( '''-''' r:=PrefixExpr { $result = F.Call((Symbol)'''-''', r, 68 | $'''-'''.StartIndex, r.Range.EndIndex); } 69 | / result:PrimaryExpr ) 70 | ]; 71 | rule LNode PrimaryExpr() @[ 72 | result:Atom 73 | ( '''(''' Expr ''')''' { $result = F.Call($result, $Expr, $result.Range.StartIndex); } 74 | | '''.''' rhs:Atom { $result = F.Dot ($result, $rhs, $result.Range.StartIndex); } 75 | )* 76 | ]; 77 | rule LNode Atom() @[ 78 | '''(''' result:Expr ''')''' { $result = F.InParens($result); } 79 | / _ { 80 | double n; 81 | $result = double.TryParse($_.Type, out n) 82 | ? F.Literal(n) : F.Id($_.Type); 83 | } 84 | ]; 85 | } 86 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/Test.out.cs: -------------------------------------------------------------------------------- 1 | // Generated from Test.ecs by LeMP custom tool. LLLPG version: 1.3.0.0 2 | // Note: you can give command-line arguments to the tool via 'Custom Tool Namespace': 3 | // --no-out-header Suppress this message 4 | // --verbose Allow verbose messages (shown by VS as 'warnings') 5 | // --macros=FileName.dll Load macros from FileName.dll, path relative to this file 6 | // Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG); 7 | using System; 8 | using System.Linq; 9 | using System.Collections.Generic; 10 | using System.Diagnostics; 11 | using Loyc; 12 | using Loyc.Syntax; 13 | using Loyc.Syntax.Lexing; 14 | struct StringToken : ISimpleToken 15 | { 16 | public string Type 17 | { 18 | get; 19 | set; 20 | } 21 | public object Value 22 | { 23 | get { 24 | return Type; 25 | } 26 | } 27 | public int StartIndex 28 | { 29 | get; 30 | set; 31 | } 32 | } 33 | class ExprParser : BaseParserForList 34 | { 35 | public ExprParser(string input) : this(input.Split(' ').Select(word => new StringToken { 36 | Type = word 37 | }).ToList()) 38 | { 39 | } 40 | public ExprParser(IList tokens, ISourceFile file = null) : base(tokens, default(StringToken), file ?? EmptySourceFile.Unknown) 41 | { 42 | F = new LNodeFactory(SourceFile); 43 | } 44 | protected override string ToString(string tokenType) 45 | { 46 | return tokenType; 47 | } 48 | LNodeFactory F = new LNodeFactory(EmptySourceFile.Unknown); 49 | LNode Op(LNode lhs, StringToken op, LNode rhs) 50 | { 51 | return F.Call((Symbol) op.Type, lhs, rhs, lhs.Range.StartIndex, rhs.Range.EndIndex); 52 | } 53 | public LNode Expr(int prec = 0) 54 | { 55 | string la0, la1; 56 | LNode got_Expr = default(LNode); 57 | StringToken lit_dash = default(StringToken); 58 | StringToken litx3D = default(StringToken); 59 | LNode result = default(LNode); 60 | LNode rhs = default(LNode); 61 | // Line 40: ("-" Expr / Atom) 62 | la0 = (string) LA0; 63 | if (la0 == "-") { 64 | la1 = (string) LA(1); 65 | if (la1 != EOF) { 66 | lit_dash = MatchAny(); 67 | var r = Expr(50); 68 | // line 40 69 | result = F.Call((Symbol) "-", r, lit_dash.StartIndex, r.Range.EndIndex); 70 | } else 71 | result = Atom(); 72 | } else 73 | result = Atom(); 74 | // Line 45: greedy( &{prec <= 10} @"=" Expr | &{prec < 20} (@"&&"|@"||") Expr | &{prec < 30} (@"!="|@"<"|@"<="|@"=="|@">"|@">=") Expr | &{prec < 40} (@"-"|@"+") Expr | &{prec < 50} (@"*"|@"/"|@"<<"|@">>") Expr | @"(" Expr @")" | @"." Atom )* 75 | for (;;) { 76 | switch ((string) LA0) { 77 | case @"=": 78 | { 79 | if (prec <= 10) { 80 | la1 = (string) LA(1); 81 | if (la1 != EOF) { 82 | litx3D = MatchAny(); 83 | var r = Expr(10); 84 | // line 47 85 | result = Op(result, litx3D, r); 86 | } else 87 | goto stop; 88 | } else 89 | goto stop; 90 | } 91 | break; 92 | case @"&&": 93 | case @"||": 94 | { 95 | if (prec < 20) { 96 | la1 = (string) LA(1); 97 | if (la1 != EOF) { 98 | var op = MatchAny(); 99 | var r = Expr(20); 100 | // line 50 101 | result = Op(result, op, r); 102 | } else 103 | goto stop; 104 | } else 105 | goto stop; 106 | } 107 | break; 108 | case @"!=": 109 | case @"<": 110 | case @"<=": 111 | case @"==": 112 | case @">": 113 | case @">=": 114 | { 115 | if (prec < 30) { 116 | la1 = (string) LA(1); 117 | if (la1 != EOF) { 118 | var op = MatchAny(); 119 | var r = Expr(30); 120 | // line 53 121 | result = Op(result, op, r); 122 | } else 123 | goto stop; 124 | } else 125 | goto stop; 126 | } 127 | break; 128 | case @"-": 129 | case @"+": 130 | { 131 | if (prec < 40) { 132 | la1 = (string) LA(1); 133 | if (la1 != EOF) { 134 | var op = MatchAny(); 135 | var r = Expr(40); 136 | // line 56 137 | result = Op(result, op, r); 138 | } else 139 | goto stop; 140 | } else 141 | goto stop; 142 | } 143 | break; 144 | case @"*": 145 | case @"/": 146 | case @"<<": 147 | case @">>": 148 | { 149 | if (prec < 50) { 150 | la1 = (string) LA(1); 151 | if (la1 != EOF) { 152 | var op = MatchAny(); 153 | var r = Expr(50); 154 | // line 59 155 | result = Op(result, op, r); 156 | } else 157 | goto stop; 158 | } else 159 | goto stop; 160 | } 161 | break; 162 | case @"(": 163 | { 164 | la1 = (string) LA(1); 165 | if (la1 != EOF) { 166 | Skip(); 167 | got_Expr = Expr(); 168 | Match(@")"); 169 | // line 61 170 | result = F.Call(result, got_Expr, result.Range.StartIndex); 171 | } else 172 | goto stop; 173 | } 174 | break; 175 | case @".": 176 | { 177 | la1 = (string) LA(1); 178 | if (la1 != EOF) { 179 | Skip(); 180 | rhs = Atom(); 181 | // line 63 182 | result = F.Dot(result, rhs, result.Range.StartIndex); 183 | } else 184 | goto stop; 185 | } 186 | break; 187 | default: 188 | goto stop; 189 | } 190 | } 191 | stop:; 192 | return result; 193 | } 194 | LNode PrefixExpr() 195 | { 196 | string la0, la1; 197 | StringToken lit_dash = default(StringToken); 198 | LNode result = default(LNode); 199 | // Line 67: (@"-" PrefixExpr / PrimaryExpr) 200 | la0 = (string) LA0; 201 | if (la0 == @"-") { 202 | la1 = (string) LA(1); 203 | if (la1 != EOF) { 204 | lit_dash = MatchAny(); 205 | var r = PrefixExpr(); 206 | // line 67 207 | result = F.Call((Symbol) "-", r, lit_dash.StartIndex, r.Range.EndIndex); 208 | } else 209 | result = PrimaryExpr(); 210 | } else 211 | result = PrimaryExpr(); 212 | return result; 213 | } 214 | LNode PrimaryExpr() 215 | { 216 | string la0; 217 | LNode got_Expr = default(LNode); 218 | LNode result = default(LNode); 219 | LNode rhs = default(LNode); 220 | result = Atom(); 221 | // Line 73: (@"(" Expr @")" | @"." Atom)* 222 | for (;;) { 223 | la0 = (string) LA0; 224 | if (la0 == @"(") { 225 | Skip(); 226 | got_Expr = Expr(); 227 | Match(@")"); 228 | // line 73 229 | result = F.Call(result, got_Expr, result.Range.StartIndex); 230 | } else if (la0 == @".") { 231 | Skip(); 232 | rhs = Atom(); 233 | // line 74 234 | result = F.Dot(result, rhs, result.Range.StartIndex); 235 | } else 236 | break; 237 | } 238 | return result; 239 | } 240 | LNode Atom() 241 | { 242 | string la0, la1; 243 | LNode result = default(LNode); 244 | StringToken tok__ = default(StringToken); 245 | // Line 78: (@"(" Expr @")" / ~(EOF)) 246 | do { 247 | la0 = (string) LA0; 248 | if (la0 == @"(") { 249 | la1 = (string) LA(1); 250 | if (la1 != EOF) { 251 | Skip(); 252 | result = Expr(); 253 | Match(@")"); 254 | // line 78 255 | result = F.InParens(result); 256 | } else 257 | goto match2; 258 | } else 259 | goto match2; 260 | break; 261 | match2: 262 | { 263 | tok__ = MatchExcept(); 264 | // line 80 265 | double n; 266 | result = double.TryParse(tok__.Type, out n) ? F.Literal(n) : F.Id(tok__.Type); 267 | } 268 | } while (false); 269 | return result; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycLibs/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycTrees/CalcExample-LoycTrees.NET40.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {DC8C2059-EC43-4A79-96CA-A90F86545BE8} 9 | Exe 10 | Properties 11 | MyLanguage 12 | LoycTreeExample 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | False 39 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Collections.dll 40 | 41 | 42 | False 43 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Essentials.dll 44 | 45 | 46 | False 47 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Syntax.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | True 55 | True 56 | MyGrammars.ecs 57 | 58 | 59 | 60 | 61 | LLLPG 62 | MyGrammars.out.cs 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycTrees/MyGrammars.ecs: -------------------------------------------------------------------------------- 1 | /* 2 | This version of the calculator parser creates a syntax tree in the form 3 | of a universal syntax tree, called a Loyc tree. Read about them here: 4 | 5 | https://github.com/qwertie/LoycCore/wiki/Loyc-trees 6 | 7 | In summary, every Loyc node (LNode) is one of three things: 8 | 9 | 1. A literal (number, string, etc) with a Value property 10 | 2. An identifier (variable name) with a Name property 11 | 3. A call (operator or function call) with an argument list (Args) 12 | 13 | LNodes also contain a source code location (source file and range of 14 | character indexes) and an optional list of attributes (which are also LNodes). 15 | One typically uses LNodeFactory to create the LNodes. After creating the 16 | syntax tree, Main() calls Compute(LNode) to run the calculation. 17 | */ 18 | #importMacros(Loyc.LLPG); // Only needed if compiling with Custom Tool = LeMP 19 | using System; 20 | using System.Text; 21 | using System.Collections.Generic; 22 | using System.Linq; 23 | using System.Diagnostics; 24 | using Loyc; // (for IMessageSink, Symbol, etc.) 25 | using Loyc.Collections; // (many handy interfaces & classes) 26 | using Loyc.Syntax.Lexing; // (for BaseLexer) 27 | using Loyc.Syntax; // (for BaseParser, LNode) 28 | 29 | namespace MyLanguage 30 | { 31 | using TT = TokenType; // Abbreviate TokenType as TT 32 | using S = CodeSymbols; 33 | 34 | // This is a table of operators that we can use more than once with `unroll`. 35 | // Associating your token types with the TokenKind enum is strictly optional 36 | // (it improves the default behavior of Token.ToString(), but it is only 37 | // required in languages like LES and EC# that support "token literals".) 38 | // Associating a Symbol like S.Add or S.Shr with each operator makes it easy 39 | // to construct the Loyc tree. 40 | replace (PUNCTUATION_TOKEN_LIST => ( 41 | (">>", Shr, S.Shr, TokenKind.Operator + 1), // Note: as a general rule, in your lexer you should list 42 | ("<<", Shl, S.Shl, TokenKind.Operator + 2), // longer operators first. Since will use this token list 43 | ("<=", LE, S.LE, TokenKind.Operator + 3), 44 | (">=", GE, S.GE, TokenKind.Operator + 4), 45 | ("==", Eq, S.Eq, TokenKind.Operator + 5), 46 | ("!=", Neq, S.Neq, TokenKind.Operator + 6), 47 | (">", GT, S.GT, TokenKind.Operator + 7), 48 | ("<", LT, S.LT, TokenKind.Operator + 8), 49 | ("&", AndBits, S.AndBits,TokenKind.Operator + 14), 50 | ("|", OrBits, S.OrBits, TokenKind.Operator + 15), 51 | ("=", Assign, S.Assign, TokenKind.Assignment), // in the lexer, longer operators are listed first here. 52 | ("^", Exp, S.Exp, TokenKind.Operator + 9), 53 | ("*", Mul, S.Mul, TokenKind.Operator + 10), 54 | ("/", Div, S.Div, TokenKind.Operator + 11), 55 | ("+", Add, S.Add, TokenKind.Operator + 12), 56 | ("-", Sub, S.Sub, TokenKind.Operator + 13), 57 | ("(", LParen, null, TokenKind.LParen), 58 | (")", RParen, null, TokenKind.RParen), 59 | (";", Semicolon,S.Semicolon, TokenKind.Separator))); 60 | 61 | // An enum containing the kinds of tokens we'll recognize. 62 | public enum TokenType 63 | { 64 | EOF = 0, // If you use EOF = 0, default(Token) represents EOF 65 | Spaces = TokenKind.Spaces + 1, 66 | Newline = TokenKind.Spaces + 2, 67 | Id = TokenKind.Id, 68 | Num = TokenKind.Literal, 69 | unroll ((_, TOKEN_NAME, _V, TOKEN_KIND) in PUNCTUATION_TOKEN_LIST) 70 | { 71 | TOKEN_NAME = TOKEN_KIND; 72 | }, 73 | Unknown 74 | } 75 | 76 | public static class TokenExt 77 | { 78 | // In this parser we'll use the predefined "Token" type in Loyc.Syntax.dll. 79 | // This extension method is defined because Token only has TypeInt, an integer. 80 | [DebuggerStepThrough] 81 | public static TokenType Type(this Token t) { return (TokenType)t.TypeInt; } 82 | } 83 | 84 | partial class MyLexer : BaseILexer 85 | { 86 | public MyLexer(UString text, string fileName = "") : this((ICharSource)text, fileName) { } 87 | public MyLexer(ICharSource text, string fileName = "") : base(text, fileName) { } 88 | 89 | public new ISourceFile SourceFile { get { return base.SourceFile; } } 90 | 91 | TokenType _type; 92 | object _value; 93 | int _startIndex; 94 | 95 | LLLPG (lexer); 96 | 97 | public override token Maybe NextToken() 98 | { 99 | _startIndex = InputPosition; 100 | _value = null; 101 | @[ {_type = TT.Num;} Num 102 | | {_type = TT.Id;} Id 103 | | {_type = TT.Newline; } Newline 104 | | {_type = TT.Spaces; } (' '|'\t')+ 105 | | any punctuation 106 | | error _? {return NoValue.Value;} 107 | ]; 108 | return new Token((int)_type, _startIndex, InputPosition - _startIndex, NodeStyle.Default, _value); 109 | } 110 | 111 | // Newline is defined in the base class, but we have to tell LLLPG what it means 112 | extern token Newline @[ '\r' '\n'? | '\n' ]; 113 | 114 | private token Id() @[ 115 | ('a'..'z'|'A'..'Z'|'_') 116 | ('a'..'z'|'A'..'Z'|'_'|'0'..'9')* 117 | {_value = (Symbol)(CharSource.Slice(_startIndex, InputPosition - _startIndex).ToString());} 118 | ]; 119 | 120 | private token Num() @[ 121 | {bool dot = false;} 122 | ('.' {dot = true;})? 123 | '0'..'9'+ 124 | (&!{dot} '.' '0'..'9'+)? 125 | {_value = double.Parse(CharSource.Slice(_startIndex, InputPosition - _startIndex).ToString());} 126 | ]; 127 | 128 | // Use 'unroll' to generate a rule for each operator token. Note: LLLPG 129 | // and 'unroll' are unaware of each other, so we cannot use 'unroll' 130 | // inside grammar code. So instead of using 'unroll' in NextToken(), I'm 131 | // creating a separate rule for each possible operator token. 132 | unroll ((TEXT, TOKEN_NAME, TOKEN_VALUE, _) in PUNCTUATION_TOKEN_LIST) 133 | { 134 | // 'extern' prevents a method from being generated for the rule. 135 | // 'inline' causes this rule to be pasted wherever it is used. 136 | // 'punctuation' is not a keyword. It is an extra tag that is 137 | // recognized by the 'any punctuation' command in NextToken(). 138 | extern inline punctuation rule TOKEN_NAME() { 139 | @[ TEXT ]; _type = TT.TOKEN_NAME; _value = TOKEN_VALUE; 140 | } 141 | } 142 | } 143 | 144 | public partial class MyParser : BaseParserForList 145 | { 146 | public static MyParser New(UString input) 147 | { 148 | var lexer = new MyLexer(input); 149 | var tokens = new List(); 150 | for (var next = lexer.NextToken(); next.HasValue; next = lexer.NextToken()) 151 | if (next.Value.Kind != TokenKind.Spaces) 152 | tokens.Add(next.Value); 153 | return new MyParser(tokens, lexer.SourceFile); 154 | } 155 | public MyParser(IList list, ISourceFile file, int startIndex = 0) 156 | : base(list, new Token((int)TT.EOF, 0, 0, null), file, startIndex) 157 | { 158 | F = new LNodeFactory(file); 159 | } 160 | 161 | // BaseParser.Match() uses this for constructing error messages. 162 | protected override string ToString(int tokenType) 163 | { 164 | return ((TokenType)tokenType).ToString(); 165 | } 166 | 167 | LNodeFactory F; 168 | LNode BinOp(Symbol type, LNode lhs, LNode rhs) 169 | { 170 | return F.Call(type, lhs, rhs, lhs.Range.StartIndex, rhs.Range.EndIndex); 171 | } 172 | 173 | // ********************************************************************* 174 | // ** Parser rules ***************************************************** 175 | // ********************************************************************* 176 | LLLPG (parser(laType(TokenType), matchType(int))); 177 | 178 | // A parser cannot directly match characters. You can, however, use 179 | // aliases like «alias(":=" = TT.Assign);» to pretend that you're 180 | // matching strings. In reality, you're still matching the tokens 181 | // produced by the lexer. Here I use 'unroll' to make an alias for 182 | // each operator and punctuation mark. 183 | unroll ((TEXT, TOKEN_NAME, _V, _K) in PUNCTUATION_TOKEN_LIST) { 184 | alias(TEXT = TT.TOKEN_NAME); 185 | } 186 | 187 | public rule LNode Start() @[ result:Expr EOF ]; 188 | 189 | // Handle multiple precedence levels with one rule, as explained in Part 5 article 190 | public rule LNode Expr(int prec = 0) @[ 191 | result:PrefixExpr 192 | greedy // to suppress ambiguity warning 193 | ( // Remember to add [Local] when your predicate uses a local variable 194 | &{[Local] prec <= 10} 195 | op:="=" r:=Expr(10) 196 | { $result = BinOp((Symbol)op.Value, $result, r); } 197 | | &{[Local] prec < 20} 198 | op:=("&"|"|") r:=Expr(20) 199 | { $result = BinOp((Symbol)op.Value, $result, r); } 200 | | &{[Local] prec < 30} 201 | op:=(">"|"<"|">="|"<="|"=="|"!=") r:=Expr(30) 202 | { $result = BinOp((Symbol)op.Value, $result, r); } 203 | | &{[Local] prec < 40} 204 | op:=("+"|"-") r:=Expr(40) 205 | { $result = BinOp((Symbol)op.Value, $result, r); } 206 | | &{[Local] prec < 50} 207 | op:=("*"|"/"|">>"|"<<") r:=Expr(50) 208 | { $result = BinOp((Symbol)op.Value, $result, r); } 209 | )* 210 | ]; 211 | 212 | private rule LNode PrefixExpr() @ 213 | [ minus:="-" Term {return F.Call(S.Sub, $Term, minus.StartIndex, $Term.Range.EndIndex);} 214 | | Term {return $Term;} 215 | ]; 216 | 217 | private rule LNode Term() @[ 218 | // Supports "mathy" expressions like 3(x-1)(x+1) 219 | result:Atom 220 | [ rest:=Atom {$result = BinOp(S.Mul, $result, rest);} ]* 221 | ]; 222 | 223 | private rule LNode Atom() @[ 224 | ( t:=TT.Id {$result = F.Id(t);} 225 | | t:=TT.Num {$result = F.Literal(t);} 226 | | "(" result:Expr() ")" {$result = F.InParens($result);} 227 | | error {$result = F.Missing; Error(0, "Expected identifer, number, or (parens)");} 228 | ) 229 | greedy [ 230 | "^" e:=Atom // exponent 231 | {$result = BinOp(S.XorBits, $result, e);} 232 | ]* 233 | ]; 234 | } 235 | } 236 | 237 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycTrees/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc.Syntax; 6 | using Loyc; 7 | using Loyc.Collections; 8 | 9 | namespace MyLanguage 10 | { 11 | using S = CodeSymbols; 12 | using Loyc.Math; 13 | 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | Console.WriteLine( 19 | "Welcome to the LLLPG-with-Loyc-trees calculator demo.\n" + 20 | "Please type an expression like pi * 2 or «exit» to quit.\n" + 21 | "Mathy-looking expressions like 2(x-1)x^2 are also accepted.\n" + 22 | "This calculator has only six built-in operators: + - * / ^ =\n" + 23 | "The = operator lets you save a result in a variable: x = 10\n"); 24 | 25 | Vars[GSymbol.Get("pi")] = Math.PI; 26 | Vars[GSymbol.Get("e")] = Math.E; 27 | 28 | string line; 29 | while ((line = Console.ReadLine()) != "exit") { 30 | while (line.EndsWith(" ")) // user can add additional lines this way 31 | line += "\n" + Console.ReadLine(); 32 | try { 33 | var parser = MyParser.New(line); 34 | LNode tree = parser.Start(); 35 | Console.WriteLine("Syntax tree: " + tree.ToString()); 36 | Console.WriteLine("Computed result: " + Compute(tree)); 37 | } catch (Exception ex) { 38 | Console.WriteLine(ex.Message); 39 | } 40 | Console.WriteLine(); 41 | } 42 | } 43 | 44 | static Dictionary Vars = new Dictionary(); 45 | static Dictionary> BinOps = new Dictionary>() 46 | { 47 | { S.XorBits, (x, y) => Math.Pow(x, y) }, 48 | { S.Mul, (x, y) => x * y }, 49 | { S.Div, (x, y) => x / y }, 50 | { S.Shr, (x, y) => MathEx.ShiftRight(x, (int)y) }, 51 | { S.Shl, (x, y) => MathEx.ShiftLeft(x, (int)y) }, 52 | { S.Add, (x, y) => x + y }, 53 | { S.Sub, (x, y) => x - y }, 54 | { S.Eq, (x, y) => x == y ? 1.0 : 0.0 }, 55 | { S.Neq, (x, y) => x != y ? 1.0 : 0.0 }, 56 | { S.GT, (x, y) => x > y ? 1.0 : 0.0 }, 57 | { S.LT, (x, y) => x < y ? 1.0 : 0.0 }, 58 | { S.GE, (x, y) => x >= y ? 1.0 : 0.0 }, 59 | { S.LE, (x, y) => x <= y ? 1.0 : 0.0 }, 60 | { S.AndBits, (x, y) => (int)x & (int)y }, 61 | { S.OrBits, (x, y) => (int)x | (int)y }, 62 | }; 63 | 64 | private static double Compute(LNode tree) 65 | { 66 | if (tree.IsLiteral) { 67 | return (double)tree.Value; 68 | } else if (tree.IsId) { 69 | double v; 70 | if (Vars.TryGetValue(tree.Name, out v)) 71 | return v; 72 | else 73 | MessageSink.Console.Write(Severity.Error, tree, "'{0}' has no value assigned.", tree.Name); 74 | } else { // IsCall 75 | if (tree.Calls(S.Assign, 2)) { 76 | if (tree.Args[0].IsId) { 77 | return Vars[tree.Args[0].Name] = Compute(tree.Args[1]); 78 | } else 79 | MessageSink.Console.Write(Severity.Error, tree, "Left hand side of '=' must be an identifier."); 80 | } else { 81 | var fn = BinOps.TryGetValue(tree.Name, null); 82 | if (fn != null && tree.ArgCount == 2) 83 | return fn(Compute(tree.Args[0]), Compute(tree.Args[1])); 84 | else 85 | MessageSink.Console.Write(Severity.Error, tree, "Operator {0} is not implemented.", tree.Name); 86 | } 87 | } 88 | return double.NaN; 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycTrees/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CalcExample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CalcExample")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b5da2d90-b3e7-4b10-a486-e6e8c5a26441")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CalcExample-UsingLoycTrees/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /EnhancedC#Parser/EcsLanguageService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc; 6 | using Loyc.Syntax; 7 | using Loyc.Utilities; 8 | using Loyc.Collections; 9 | using Loyc.Syntax.Lexing; 10 | using Loyc.Ecs.Parser; 11 | 12 | namespace Loyc.Ecs 13 | { 14 | /// The property provides easy access to the lexer, 15 | /// parser and printer for Enhanced C#. 16 | /// 17 | /// EC# overview: https://sourceforge.net/apps/mediawiki/loyc/index.php?title=Ecs 18 | /// 19 | public class EcsLanguageService : IParsingService 20 | { 21 | static readonly string[] _fileExtensionsNormal = new[] { "ecs", "cs" }; 22 | static readonly string[] _fileExtensionsPlainCs = new[] { "cs", "ecs" }; 23 | 24 | public static readonly EcsLanguageService Value = new EcsLanguageService(); 25 | 26 | readonly string[] _fileExtensions = _fileExtensionsNormal; 27 | // We have no printer in this project; use LES as a fallback 28 | readonly LNodePrinter _printer = Loyc.Syntax.Les.LesNodePrinter.Printer; 29 | readonly string _name = "Enhanced C# (parser only)"; 30 | 31 | private EcsLanguageService() 32 | { 33 | } 34 | 35 | public override string ToString() 36 | { 37 | return _name; 38 | } 39 | 40 | public IEnumerable FileExtensions { get { return _fileExtensions; } } 41 | 42 | public LNodePrinter Printer 43 | { 44 | get { return _printer; } 45 | } 46 | public string Print(LNode node, IMessageSink msgs = null, object mode = null, string indentString = "\t", string lineSeparator = "\n") 47 | { 48 | var sb = new StringBuilder(); 49 | _printer(node, sb, msgs ?? MessageSink.Current, mode, indentString, lineSeparator); 50 | return sb.ToString(); 51 | } 52 | public bool HasTokenizer 53 | { 54 | get { return true; } 55 | } 56 | public ILexer Tokenize(ICharSource text, string fileName, IMessageSink msgs) 57 | { 58 | return new EcsLexer(text, fileName, msgs); 59 | } 60 | public IListSource Parse(ICharSource text, string fileName, IMessageSink msgs, ParsingMode inputType = null) 61 | { 62 | var lexer = Tokenize(text, fileName, msgs); 63 | return Parse(lexer, msgs, inputType); 64 | } 65 | public IListSource Parse(ILexer input, IMessageSink msgs, ParsingMode inputType = null) 66 | { 67 | var preprocessed = new EcsPreprocessor(input); 68 | var treeified = new TokensToTree(preprocessed, false); 69 | return Parse(treeified.Buffered(), input.SourceFile, msgs, inputType); 70 | } 71 | 72 | [ThreadStatic] 73 | static EcsParser _parser; 74 | 75 | public IListSource Parse(IListSource input, ISourceFile file, IMessageSink msgs, ParsingMode inputType = null) 76 | { 77 | // For efficiency we'd prefer to re-use our _parser object, but 78 | // when parsing lazily, we can't re-use it because another parsing 79 | // operation could start before this one is finished. To force 80 | // greedy parsing, we can call ParseStmtsGreedy(), but the caller may 81 | // prefer lazy parsing, especially if the input is large. As a 82 | // compromise I'll check if the source file is larger than a 83 | // certain arbitrary size. Also, ParseExprs() is always greedy 84 | // so we can always re-use _parser in that case. 85 | char _ = '\0'; 86 | if (file.Text.TryGet(255, ref _) || inputType == ParsingMode.FormalArguments || 87 | inputType == ParsingMode.Types || inputType == ParsingMode.Expressions) { 88 | EcsParser parser = _parser; 89 | if (parser == null) 90 | _parser = parser = new EcsParser(input, file, msgs); 91 | else { 92 | parser.ErrorSink = msgs ?? MessageSink.Current; 93 | parser.Reset(input, file); 94 | } 95 | if (inputType == ParsingMode.Expressions) 96 | return parser.ParseExprs(false, allowUnassignedVarDecl: false); 97 | else if (inputType == ParsingMode.FormalArguments) 98 | return parser.ParseExprs(false, allowUnassignedVarDecl: true); 99 | else if (inputType == ParsingMode.Types) 100 | return LNode.List(parser.DataType()); 101 | else 102 | return parser.ParseStmtsGreedy(); 103 | } else { 104 | var parser = new EcsParser(input, file, msgs); 105 | return parser.ParseStmtsLazy().Buffered(); 106 | } 107 | } 108 | } 109 | } 110 | 111 | -------------------------------------------------------------------------------- /EnhancedC#Parser/EcsLexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using Loyc; 8 | using Loyc.Collections; 9 | using Loyc.Collections.Impl; 10 | using Loyc.Threading; 11 | using Loyc.Utilities; 12 | using Loyc.Syntax; 13 | using Loyc.Syntax.Lexing; 14 | using Loyc.Syntax.Les; 15 | 16 | namespace Loyc.Ecs.Parser 17 | { 18 | using TT = TokenType; 19 | 20 | /// Lexer for EC# source code (see ). 21 | /// 22 | public partial class EcsLexer : BaseLexer, ILexer 23 | { 24 | public EcsLexer(string text, IMessageSink sink) : base(new UString(text), "") { ErrorSink = sink; } 25 | public EcsLexer(ICharSource text, string fileName, IMessageSink sink, int startPosition = 0) : base(text, fileName, startPosition) { ErrorSink = sink; } 26 | 27 | public bool AllowNestedComments = false; 28 | private bool _isFloat, _parseNeeded, _verbatim; 29 | // Alternate: hex numbers, verbatim strings 30 | // UserFlag: bin numbers, double-verbatim 31 | private NodeStyle _style; 32 | private int _numberBase; 33 | private Symbol _typeSuffix; 34 | private TokenType _type; // predicted type of the current token 35 | private object _value; 36 | private int _startPosition; 37 | // _allowPPAt is used to detect whether a preprocessor directive is allowed 38 | // at the current input position. When _allowPPAt==_startPosition, it's allowed. 39 | private int _allowPPAt; 40 | 41 | public void Reset(ICharSource source, string fileName = "", int inputPosition = 0) 42 | { 43 | base.Reset(source, fileName, inputPosition, true); 44 | } 45 | 46 | public new ISourceFile SourceFile { get { return base.SourceFile; } } 47 | 48 | int _indentLevel; 49 | UString _indent; 50 | public int IndentLevel { get { return _indentLevel; } } 51 | public UString IndentString { get { return _indent; } } 52 | public int SpacesPerTab = 4; 53 | 54 | public Maybe NextToken() 55 | { 56 | _startPosition = InputPosition; 57 | _value = null; 58 | _style = 0; 59 | if (InputPosition >= CharSource.Count) 60 | return Maybe.NoValue; 61 | else { 62 | Token(); 63 | Debug.Assert(InputPosition > _startPosition); 64 | return new Token((int)_type, _startPosition, InputPosition - _startPosition, _style, _value); 65 | } 66 | } 67 | 68 | protected override void Error(int lookaheadIndex, string message) 69 | { 70 | // the fast "blitting" code path may not be able to handle errors 71 | _parseNeeded = true; 72 | 73 | var pos = SourceFile.IndexToLine(InputPosition + lookaheadIndex); 74 | if (ErrorSink != null) 75 | ErrorSink.Write(Severity.Error, pos, message); 76 | else 77 | throw new FormatException(pos + ": " + message); 78 | } 79 | 80 | public void Restart() 81 | { 82 | _indentLevel = 0; 83 | _lineNumber = 0; 84 | _allowPPAt = _lineStartAt = 0; 85 | } 86 | 87 | #region Value parsers 88 | // After the generated lexer code determines the boundaries of the token, 89 | // one of these methods extracts the value of the token (e.g. "17L" => (long)17) 90 | // There are value parsers for identifiers, numbers, and strings; certain 91 | // parser cores are also accessible as public static methods. 92 | 93 | #region String parsing 94 | 95 | void ParseSQStringValue() 96 | { 97 | int len = InputPosition - _startPosition; 98 | if (!_parseNeeded && len == 3) { 99 | _value = CG.Cache(CharSource[_startPosition + 1]); 100 | } else { 101 | string s = ParseStringCore(_startPosition); 102 | _value = s; 103 | if (s.Length == 1) 104 | _value = CG.Cache(s[0]); 105 | else if (s.Length == 0) 106 | Error(_startPosition, "Empty character literal".Localized()); 107 | else 108 | Error(_startPosition, "Character literal has {0} characters (there should be exactly one)".Localized(s.Length)); 109 | } 110 | } 111 | 112 | void ParseBQStringValue() 113 | { 114 | var value = ParseStringCore(_startPosition); 115 | _value = GSymbol.Get(value.ToString()); 116 | } 117 | 118 | void ParseStringValue() 119 | { 120 | _value = ParseStringCore(_startPosition); 121 | if (_value.ToString().Length < 16) 122 | _value = CG.Cache(_value); 123 | } 124 | 125 | string ParseStringCore(int start) 126 | { 127 | Debug.Assert(_verbatim == (CharSource[start] == '@')); 128 | if (_verbatim) 129 | start++; 130 | char q; 131 | Debug.Assert((q = CharSource.TryGet(start, '\0')) == '"' || q == '\'' || q == '`'); 132 | bool tripleQuoted = (_style & NodeStyle.Alternate2) != 0; 133 | 134 | string value; 135 | if (!_parseNeeded) { 136 | Debug.Assert(!tripleQuoted); 137 | value = (string)CharSource.Slice(start + 1, InputPosition - start - 2).ToString(); 138 | } else { 139 | UString original = CharSource.Slice(start, InputPosition - start); 140 | value = UnescapeQuotedString(ref original, _verbatim, Error, _indent); 141 | } 142 | return value; 143 | } 144 | 145 | static string UnescapeQuotedString(ref UString source, bool isVerbatim, Action onError, UString indentation) 146 | { 147 | Debug.Assert(source.Length >= 1); 148 | if (isVerbatim) { 149 | bool fail; 150 | char stringType = (char)source.PopFront(out fail); 151 | StringBuilder sb = new StringBuilder(); 152 | int c; 153 | for (; ; ) { 154 | c = source.PopFront(out fail); 155 | if (fail) break; 156 | if (c == stringType) { 157 | if ((c = source.PopFront(out fail)) != stringType) 158 | break; 159 | } 160 | sb.Append((char)c); 161 | } 162 | return sb.ToString(); 163 | } else { 164 | // triple-quoted or normal string: let LES lexer handle it 165 | return LesLexer.UnescapeQuotedString(ref source, onError, indentation, true); 166 | } 167 | } 168 | 169 | #endregion 170 | 171 | #region Identifier & Symbol parsing (including public ParseIdentifier()) 172 | 173 | // id & symbol cache. For Symbols, includes only one of the two @ signs. 174 | protected Dictionary _idCache = new Dictionary(); 175 | 176 | void ParseIdValue(int skipAt, bool isBQString) 177 | { 178 | ParseIdOrSymbol(_startPosition + skipAt, isBQString); 179 | } 180 | void ParseSymbolValue(bool isBQString) 181 | { 182 | ParseIdOrSymbol(_startPosition + 2, isBQString); 183 | } 184 | 185 | void ParseIdOrSymbol(int start, bool isBQString) 186 | { 187 | UString unparsed = CharSource.Slice(start, InputPosition - start); 188 | UString parsed; 189 | Debug.Assert(isBQString == (CharSource.TryGet(start, '\0') == '`')); 190 | Debug.Assert(!_verbatim); 191 | if (!_idCache.TryGetValue(unparsed, out _value)) { 192 | if (isBQString) 193 | parsed = ParseStringCore(start); 194 | else if (_parseNeeded) 195 | parsed = ScanNormalIdentifier(unparsed); 196 | else 197 | parsed = unparsed; 198 | _idCache[unparsed.ShedExcessMemory(50)] = _value = GSymbol.Get(parsed.ToString()); 199 | } 200 | } 201 | 202 | static string ScanNormalIdentifier(UString text) 203 | { 204 | var parsed = new StringBuilder(); 205 | char c; 206 | while ((c = text[0, '\0']) != '\0') { 207 | if (!ScanUnicodeEscape(ref text, parsed, c)) { 208 | parsed.Append(c); 209 | text = text.Slice(1); 210 | } 211 | } 212 | return parsed.ToString(); 213 | } 214 | static bool ScanUnicodeEscape(ref UString text, StringBuilder parsed, char c) 215 | { 216 | // I can't imagine why this exists in C# in the first place. Unicode 217 | // escapes inside identifiers are required to be letters or digits, 218 | // although my lexer doesn't enforce this (EC# needs no such rule.) 219 | if (c != '\\') 220 | return false; 221 | char u = text.TryGet(1, '\0'); 222 | int len = 4; 223 | if (u == 'u' || u == 'U') { 224 | if (u == 'U') len = 8; 225 | if (text.Length < 2 + len) 226 | return false; 227 | 228 | var digits = text.Substring(2, len); 229 | int code; 230 | if (ParseHelpers.TryParseHex(digits, out code) && code <= 0x0010FFFF) { 231 | if (code >= 0x10000) { 232 | parsed.Append((char)(0xD800 + ((code - 0x10000) >> 10))); 233 | parsed.Append((char)(0xDC00 + ((code - 0x10000) & 0x3FF))); 234 | } else 235 | parsed.Append((char)code); 236 | text = text.Substring(2 + len); 237 | return true; 238 | } 239 | } 240 | return false; 241 | } 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | //private bool FindCurrentIdInKeywordTrie(Trie t, string source, int start, ref Symbol value, ref TokenType type) 252 | //{ 253 | // Debug.Assert(InputPosition >= start); 254 | // for (int i = start, stop = InputPosition; i < stop; i++) { 255 | // char input = source[i]; 256 | // int input_i = input - t.CharOffs; 257 | // if (t.Child == null || (uint)input_i >= t.Child.Length) { 258 | // if (input == '\'' && t.Value != null) { 259 | // // Detected keyword followed by single quote. This requires 260 | // // the lexer to backtrack so that, for example, case'x' is 261 | // // treated as two tokens instead of the one token it 262 | // // initially appears to be. 263 | // InputPosition = i; 264 | // break; 265 | // } 266 | // return false; 267 | // } 268 | // if ((t = t.Child[input - t.CharOffs]) == null) 269 | // return false; 270 | // } 271 | // if (t.Value != null) { 272 | // value = t.Value; 273 | // type = t.TokenType; 274 | // return true; 275 | // } 276 | // return false; 277 | //} 278 | 279 | 280 | #endregion 281 | 282 | #region Number parsing 283 | 284 | static Symbol _sub = GSymbol.Get("-"); 285 | static Symbol _F = GSymbol.Get("F"); 286 | static Symbol _D = GSymbol.Get("D"); 287 | static Symbol _M = GSymbol.Get("M"); 288 | static Symbol _U = GSymbol.Get("U"); 289 | static Symbol _L = GSymbol.Get("L"); 290 | static Symbol _UL = GSymbol.Get("UL"); 291 | 292 | void ParseNumberValue() 293 | { 294 | int start = _startPosition; 295 | if (_numberBase != 10) 296 | start += 2; 297 | int stop = InputPosition; 298 | if (_typeSuffix != null) 299 | stop -= _typeSuffix.Name.Length; 300 | 301 | UString digits = CharSource.Slice(start, stop - start); 302 | string error; 303 | if ((_value = LesLexer.ParseNumberCore(digits, false, _numberBase, _isFloat, _typeSuffix, out error)) == null) 304 | _value = 0; 305 | else if (_value == CodeSymbols.Sub) { 306 | InputPosition = _startPosition + 1; 307 | _type = TT.Sub; 308 | } 309 | if (error != null) 310 | Error(_startPosition, error); 311 | } 312 | 313 | #endregion 314 | 315 | #endregion 316 | 317 | // Due to the way generics are implemented, repeating the implementation 318 | // of this base-class method might improve performance (TODO: verify this idea) 319 | new protected int LA(int i) 320 | { 321 | bool fail; 322 | char result = CharSource.TryGet(InputPosition + i, out fail); 323 | return fail ? -1 : result; 324 | } 325 | 326 | int MeasureIndent(UString indent) 327 | { 328 | return MeasureIndent(indent, SpacesPerTab); 329 | } 330 | public static int MeasureIndent(UString indent, int spacesPerTab) 331 | { 332 | int amount = 0; 333 | for (int i = 0; i < indent.Length; i++) { 334 | char ch = indent[i]; 335 | if (ch == '\t') { 336 | amount += spacesPerTab; 337 | amount -= amount % spacesPerTab; 338 | } else if (ch == '.' && i + 1 < indent.Length) { 339 | amount += spacesPerTab; 340 | amount -= amount % spacesPerTab; 341 | i++; 342 | } else 343 | amount++; 344 | } 345 | return amount; 346 | } 347 | 348 | Maybe _current; 349 | 350 | void IDisposable.Dispose() { } 351 | Token IEnumerator.Current { get { return _current.Value; } } 352 | object System.Collections.IEnumerator.Current { get { return _current; } } 353 | void System.Collections.IEnumerator.Reset() { throw new NotSupportedException(); } 354 | bool System.Collections.IEnumerator.MoveNext() 355 | { 356 | _current = NextToken(); 357 | return _current.HasValue; 358 | } 359 | } 360 | 361 | } 362 | -------------------------------------------------------------------------------- /EnhancedC#Parser/EcsPrecedence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics; 6 | using Loyc; 7 | using Loyc.Math; 8 | using Loyc.Syntax; 9 | 10 | namespace Loyc.Ecs 11 | { 12 | /// Contains objects that represent the 13 | /// precedence rules of EC#. 14 | /// 15 | /// Summary: 16 | ///
100+: Primary: x.y x::y f(x) a[i] etc. 17 | ///
90+: Prefix: + - ! ~ ++x --x (T)x 18 | ///
80+: Power: x**y 19 | ///
70+: Mult: * / % 20 | ///
60+: Add: + - (Shift is 56 but ideally would be 70) 21 | ///
50+: Range: .. (`custom operators` are 28 to 55) 22 | ///
40+: Compare: < > <= >= is as using == != 23 | ///
30+: Bitwise &^| (Ideally would be 54..59) 24 | ///
20+: Conditional && || ^^ 25 | ///
10+: Ternary 26 | ///
1: Assignment 27 | ///
-1: Lambda (only the right-hand side of '=>') 28 | /// 29 | /// When printing an expression, we avoid emitting x & y == z because 30 | /// the ranges of == and & overlap. Instead prints 31 | /// #&(x, y == z). Admittedly this is rather ugly, but you can enable 32 | /// the option, which allows 33 | /// parenthesis to be added so that a Loyc tree with the structure 34 | /// #&(x, y == z) is emitted as x & (y == z), even though the 35 | /// latter is a slightly different tree. 36 | /// 37 | /// Most of the operators use a range of two adjacent numbers, e.g. 10..11. 38 | /// This represents a couple of ideas for future use in a compiler that allows 39 | /// you to define new operators; one idea is, you could give new operators the 40 | /// "same" precedence as existing operators, but make them immiscible with 41 | /// those operators... yet still make them miscible with another new operator. 42 | /// For instance, suppose you define two new operators `glob` and `fup` with 43 | /// PrecedenceRange 41..41 and 40..40 respectively. Then neither can be mixed 44 | /// with + and -, but they can be mixed with each other and `fup` has higher 45 | /// precedence. Maybe this is not very useful, but hey, why not? If simply 46 | /// incrementing a number opens up new extensibility features, I'm happy to 47 | /// do it. (I could have used a non-numeric partial ordering system to do 48 | /// the same thing, but it would have been more complex, and of questionable 49 | /// value.) 50 | ///
51 | /// 52 | public static class EcsPrecedence 53 | { 54 | public static readonly Precedence TightAttr = Precedence.MaxValue; 55 | public static readonly Precedence Substitute = new Precedence(103, 102, 102, 103);// $x .x 56 | public static readonly Precedence Primary = new Precedence(100); // x.y x::y x=:y x->y f(x) x(->y) a[x] x++ x-- typeof() checked() unchecked() new 57 | public static readonly Precedence NullDot = new Precedence(99); // ?. 58 | public static readonly Precedence Prefix = new Precedence(91, 90, 90, 91); // + - ! ~ ++x --x (T)x 59 | public static readonly Precedence Forward = new Precedence(88); // ==>x 60 | public static readonly Precedence Power = new Precedence(80); // ** 61 | public static readonly Precedence Multiply = new Precedence(70); // *, /, % 62 | public static readonly Precedence Add = new Precedence(60); // +, -, ~ 63 | public static readonly Precedence Shift = new Precedence(56, 56, 56, 70); // >> << (for printing purposes, immiscible with * / + -) 64 | public static readonly Precedence Range = new Precedence(50); // .. 65 | public static readonly Precedence Backtick = new Precedence(46, 72, 45, 73); // `custom operator` (immiscible with * / + - << >> ..) 66 | public static readonly Precedence Compare = new Precedence(40); // < > <= >= 67 | public static readonly Precedence IsAsUsing = new Precedence(40, 99, 40, 40); // is as using 68 | public static readonly new Precedence Equals = new Precedence(38); // == != in 69 | public static readonly Precedence AndBits = new Precedence(32, 32, 32, 45); // & (^ and | should not be mixed with Compare/Equals 70 | public static readonly Precedence XorBits = new Precedence(30, 30, 32, 45); // ^ either, but the low-high system cannot express this 71 | public static readonly Precedence OrBits = new Precedence(28, 28, 32, 45); // | while allowing & ^ | to be mixed with each other.) 72 | public static readonly Precedence And = new Precedence(22); // && 73 | public static readonly Precedence Or = new Precedence(20); // || ^^ 74 | public static readonly Precedence OrIfNull = new Precedence(16); // ?? 75 | public static readonly Precedence IfElse = new Precedence(11, 10, 10, 11); // x ? y : z 76 | public static readonly Precedence Assign = new Precedence(35, 0, 0, 1); // = *= /= %= += -= <<= >>= &= ^= |= ??= ~= 77 | public static readonly Precedence Lambda = new Precedence(85, -1, -2, -1); // => 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /EnhancedC#Parser/EcsPreprocessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc; 6 | using Loyc.Utilities; 7 | using Loyc.Syntax; 8 | using Loyc.Syntax.Lexing; 9 | using Loyc.Collections; 10 | using Loyc.Collections.Impl; 11 | using System.Collections; 12 | 13 | namespace Loyc.Ecs.Parser 14 | { 15 | using S = CodeSymbols; 16 | 17 | /// Handles EC# processor directives. 18 | /// This class not only preprocesses C# source code, it saves 19 | /// preprocessor directives and comments so that any code excluded by the 20 | /// preprocessor can be added back in later, if and when the parsed code is 21 | /// printed out. For example, given input like this: 22 | /// 23 | /// void foo // see below 24 | /// #if false 25 | /// invalid code! 26 | /// #endif 27 | /// () { Console.WriteLine("foo()!"); } 28 | /// 29 | /// EcsPreprocessor removes the #if...#endif region of tokens, creates a 30 | /// single Token of type TokenType.PPFalseBlock to represent that region, and 31 | /// saves it, after the "see below" comment token, in a list. 32 | /// 33 | /// C# has the following preprocessor directives: 34 | /// 35 | /// #define Id 36 | /// #undef Id 37 | /// #if expr 38 | /// #elif expr 39 | /// #else 40 | /// #endif 41 | /// #warning {arbitrary text} 42 | /// #error {arbitrary text} 43 | /// #region {arbitrary text} 44 | /// #endregion 45 | /// #line 123 "filename" 46 | /// #pragma warning ... 47 | /// #pragma ... // ignored 48 | /// 49 | /// 50 | public class EcsPreprocessor : LexerWrapper 51 | { 52 | public EcsPreprocessor(ILexer source, Action onComment = null) 53 | : base(source) { _onComment = onComment; } 54 | 55 | // Can't use ISet: it is new in .NET 4, but HashSet is new in .NET 3.5 56 | public HashSet DefinedSymbols = new HashSet(); 57 | 58 | Action _onComment; 59 | List _commentList = new List(); 60 | public IList CommentList { get { return _commentList; } } 61 | 62 | EcsParser _parser = null; 63 | 64 | // Holds the remainder of a preprocessor line. This will be empty for 65 | // #region, #error, #warning and #note, as the lexer reads the rest of 66 | // of these lines as text and stores that text as the PP token's Value. 67 | List _rest = new List(); 68 | private void ReadRest() 69 | { 70 | _rest.Clear(); 71 | for (; ; ) { 72 | Maybe t = Lexer.NextToken(); 73 | if (!t.HasValue || t.Value.Type() == TokenType.Newline) 74 | break; 75 | else if (!t.Value.IsWhitespace) 76 | _rest.Add(t.Value); 77 | } 78 | } 79 | 80 | Stack> _ifRegions = new Stack>(); 81 | Stack _regions = new Stack(); 82 | 83 | public override Maybe NextToken() 84 | { 85 | do { 86 | Maybe t_ = Lexer.NextToken(); 87 | redo: 88 | if (!t_.HasValue) 89 | break; 90 | var t = t_.Value; 91 | if (t.IsWhitespace) { 92 | if (t.Kind == TokenKind.Comment) 93 | AddComment(t); 94 | continue; 95 | } else if (t.Kind == TokenKind.Other) { 96 | switch (t.Type()) { 97 | case TokenType.PPdefine: 98 | case TokenType.PPundef: 99 | ReadRest(); 100 | bool undef = t.Type() == TokenType.PPundef; 101 | if (_rest.Count == 1 && _rest[0].Type() == TokenType.Id) { 102 | if (undef) 103 | DefinedSymbols.Remove((Symbol)_rest[0].Value); 104 | else 105 | DefinedSymbols.Add((Symbol)_rest[0].Value); 106 | } else 107 | ErrorSink.Write(Severity.Error, t.ToSourceRange(SourceFile), "'{0}' should be followed by a single, simple identifier", undef ? "#undef" : "#define"); 108 | continue; 109 | case TokenType.PPif: 110 | var tree = ReadRestAsTokenTree(); 111 | LNode expr = ParseExpr(tree); 112 | 113 | var cond = Evaluate(expr) ?? false; 114 | _ifRegions.Push(Pair.Create(t, cond)); 115 | t_ = SaveDirectiveAndAutoSkip(t, cond); 116 | goto redo; 117 | case TokenType.PPelse: 118 | case TokenType.PPelif: 119 | var tree_ = ReadRestAsTokenTree(); 120 | 121 | if (_ifRegions.Count == 0) { 122 | ErrorSink.Write(Severity.Error, t.ToSourceRange(SourceFile), 123 | "Missing #if clause before '{0}'", t); 124 | _ifRegions.Push(Pair.Create(t, false)); 125 | } 126 | bool isElif = t.Type() == TokenType.PPelif, hasExpr = tree_.HasIndex(0); 127 | if (hasExpr != isElif) 128 | Error(t, isElif ? "Missing condition on #elif" : "Unexpected tokens after #else"); 129 | bool cond_ = true; 130 | if (hasExpr) { 131 | LNode expr_ = ParseExpr(tree_); 132 | cond_ = Evaluate(expr_) ?? false; 133 | } 134 | if (_ifRegions.Peek().B) 135 | cond_ = false; 136 | t_ = SaveDirectiveAndAutoSkip(t, cond_); 137 | if (cond_) 138 | _ifRegions.Push(Pair.Create(_ifRegions.Pop().A, cond_)); 139 | goto redo; 140 | case TokenType.PPendif: 141 | var tree__ = ReadRestAsTokenTree(); 142 | if (_ifRegions.Count == 0) 143 | Error(t, "Missing #if before #endif"); 144 | else { 145 | _ifRegions.Pop(); 146 | if (tree__.Count > 0) 147 | Error(t, "Unexpected tokens after #endif"); 148 | } 149 | _commentList.Add(t); 150 | continue; 151 | case TokenType.PPerror: 152 | _commentList.Add(t); 153 | Error(t, t.Value.ToString()); 154 | continue; 155 | case TokenType.PPwarning: 156 | _commentList.Add(t); 157 | ErrorSink.Write(Severity.Warning, t.ToSourceRange(SourceFile), t.Value.ToString()); 158 | continue; 159 | case TokenType.PPregion: 160 | _commentList.Add(t); 161 | _regions.Push(t); 162 | continue; 163 | case TokenType.PPendregion: 164 | _commentList.Add(t); 165 | if (_regions.Count == 0) 166 | ErrorSink.Write(Severity.Warning, t.ToSourceRange(SourceFile), "#endregion without matching #region"); 167 | else 168 | _regions.Pop(); 169 | continue; 170 | case TokenType.PPline: 171 | _commentList.Add(new Token(t.TypeInt, t.StartIndex, Lexer.InputPosition)); 172 | var rest = ReadRestAsTokenTree(); 173 | // TODO 174 | ErrorSink.Write(Severity.Note, t.ToSourceRange(SourceFile), "Support for #line is not implemented"); 175 | continue; 176 | case TokenType.PPpragma: 177 | _commentList.Add(new Token(t.TypeInt, t.StartIndex, Lexer.InputPosition)); 178 | var rest_ = ReadRestAsTokenTree(); 179 | // TODO 180 | ErrorSink.Write(Severity.Note, t.ToSourceRange(SourceFile), "Support for #pragma is not implemented"); 181 | continue; 182 | } 183 | } 184 | return t_; 185 | } while (true); 186 | // end of stream 187 | if (_ifRegions.Count > 0) 188 | ErrorSink.Write(Severity.Error, _ifRegions.Peek().A.ToSourceRange(SourceFile), "#if without matching #endif"); 189 | if (_regions.Count > 0) 190 | ErrorSink.Write(Severity.Warning, _regions.Peek().ToSourceRange(SourceFile), "#region without matching #endregion"); 191 | return Maybe.NoValue; 192 | } 193 | 194 | private void AddComment(Token t) 195 | { 196 | if (_commentList != null) 197 | _commentList.Add(t); 198 | if (_onComment != null) 199 | _onComment(t); 200 | } 201 | 202 | private void Error(Token pptoken, string message) 203 | { 204 | ErrorSink.Write(Severity.Error, pptoken.ToSourceRange(SourceFile), message); 205 | } 206 | 207 | private Maybe SaveDirectiveAndAutoSkip(Token pptoken, bool cond) 208 | { 209 | _commentList.Add(new Token(pptoken.TypeInt, pptoken.StartIndex, Lexer.InputPosition)); 210 | if (!cond) 211 | return SkipIgnoredRegion(); 212 | else 213 | return Lexer.NextToken(); 214 | } 215 | 216 | private LNode ParseExpr(IListSource tree) 217 | { 218 | if (_parser == null) _parser = new EcsParser(tree, SourceFile, ErrorSink); 219 | else _parser.Reset(tree, SourceFile); 220 | LNode expr = _parser.ExprStart(false); 221 | return expr; 222 | } 223 | 224 | // Skips over a region that has is within a "false" #if/#elif/#else region. 225 | // The region (not including the leading or trailing #if/#elif/#else/#endif) 226 | // is added to _commentList as a single "token" of type TokenType.PPignored. 227 | private Maybe SkipIgnoredRegion() 228 | { 229 | int nestedIfs = 0; 230 | int startIndex = Lexer.InputPosition; 231 | Maybe t_; 232 | while ((t_ = Lexer.NextToken()).HasValue) { 233 | var t = t_.Value; 234 | if (t.Type() == TokenType.PPif) 235 | nestedIfs++; 236 | else if (t.Type() == TokenType.PPendif && --nestedIfs < 0) 237 | break; 238 | else if ((t.Type() == TokenType.PPelif || t.Type() == TokenType.PPelse) && nestedIfs == 0) 239 | break; 240 | } 241 | int stopIndex = t_.HasValue ? t_.Value.StartIndex : Lexer.InputPosition; 242 | _commentList.Add(new Token((int)TokenType.PPignored, startIndex, stopIndex - startIndex)); 243 | return t_; 244 | } 245 | 246 | private bool? Evaluate(LNode expr) 247 | { 248 | if (expr.IsId) 249 | return DefinedSymbols.Contains(expr.Name); 250 | else if (expr.IsLiteral && expr.Value is bool) 251 | return (bool)expr.Value; 252 | else if (expr.Calls(S.And, 2)) 253 | return Evaluate(expr.Args[0]) & Evaluate(expr.Args[1]); 254 | else if (expr.Calls(S.Or, 2)) 255 | return Evaluate(expr.Args[0]) | Evaluate(expr.Args[1]); 256 | else if (expr.Calls(S.Not, 1)) 257 | return !Evaluate(expr.Args[0]); 258 | else if (expr.Calls(S.Eq, 2)) 259 | return Evaluate(expr.Args[0]) == Evaluate(expr.Args[1]); 260 | else if (expr.Calls(S.Neq, 2)) 261 | return Evaluate(expr.Args[0]) != Evaluate(expr.Args[1]); 262 | else { 263 | ErrorSink.Write(Severity.Error, expr.Range, "Only simple boolean expressions with &&, ||, !, ==, !=, are supported in #if and #elif"); 264 | return null; 265 | } 266 | } 267 | 268 | private IListSource ReadRestAsTokenTree() 269 | { 270 | ReadRest(); 271 | var restAsLexer = new TokenListAsLexer(_rest, Lexer.SourceFile); 272 | var treeLexer = new TokensToTree(restAsLexer, false); 273 | return treeLexer.Buffered(); 274 | } 275 | } 276 | 277 | /// A helper class that removes comments from a token stream, saving 278 | /// them into a list. This class deletes whitespace, but adds tokens to a list. 279 | public class CommentSaver : LexerWrapper 280 | { 281 | public CommentSaver(ILexer source, IList commentList = null) 282 | : base(source) { _commentList = commentList ?? new List(); } 283 | 284 | IList _commentList; 285 | public IList CommentList { get { return _commentList; } } 286 | 287 | public sealed override Maybe NextToken() 288 | { 289 | Maybe t = Lexer.NextToken(); 290 | for (; ; ) { 291 | t = Lexer.NextToken(); 292 | if (!t.HasValue) 293 | break; 294 | else if (t.Value.IsWhitespace) { 295 | if (t.Value.Kind == TokenKind.Comment) { 296 | _commentList.Add(t.Value); 297 | } 298 | } else 299 | break; 300 | } 301 | return t; 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /EnhancedC#Parser/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc.Syntax; 6 | using Loyc; 7 | using Loyc.Collections; 8 | 9 | namespace Loyc.Ecs 10 | { 11 | using S = CodeSymbols; 12 | using Loyc.Syntax.Lexing; 13 | 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | Console.WriteLine( 19 | "Welcome to EC# parser demo.\n\n" + 20 | "Please type a valid C# or EC# statement such as x = 7; or class Man:Beast {}\n" + 21 | "or while(true) Console.WriteLine(\"I'm not crazy!\"); (or exit to quit).\n"); 22 | Console.WriteLine( 23 | "The parser produces Loyc trees, and they are printed out using the default\n" + 24 | "printer which is the LES printer.\n"); 25 | Console.WriteLine( 26 | "The parser was ripped out of Loyc.Ecs.dll for this demo. Loyc.Ecs.dll, unlike \n" + 27 | "this demo, also contains the EC# printer (which LLLPG uses to write output).\n"); 28 | Console.WriteLine( 29 | "Exercise for the reader: write a REPL for C#. Just kidding.\n"); 30 | 31 | string line; 32 | while ((line = Console.ReadLine()) != "exit") { 33 | while (line.EndsWith(" ")) // user can add additional lines this way 34 | line += "\n" + Console.ReadLine(); 35 | try { 36 | // Parse EC#! 37 | IListSource stmts = EcsLanguageService.Value.Parse(line, MessageSink.Console); 38 | // If you'd like to parse LES instead of EC#, write this instead: 39 | // IListSource stmts = Loyc.Syntax.Les.LesLanguageService.Value.Parse(line, MessageSink.Console); 40 | 41 | var c = Console.ForegroundColor; 42 | Console.ForegroundColor = ConsoleColor.Cyan; 43 | foreach (var stmt in stmts) 44 | Console.WriteLine("Syntax tree (LES format): " + stmt.ToString()); 45 | Console.ForegroundColor = c; 46 | } catch (Exception ex) { 47 | Console.WriteLine(ex.GetType().Name + ": " + ex.Message); 48 | } 49 | Console.WriteLine(); 50 | } 51 | } 52 | } 53 | 54 | partial class EcsValidators 55 | { 56 | /// Given a complex name such as global::Foo<int>.Bar<T>, 57 | /// this method identifies the base name component, which in this example 58 | /// is Bar. This is used, for example, to identify the expected name for 59 | /// a constructor based on the class name, e.g. Foo<T> => Foo. 60 | /// It is not verified that name is a complex identifier. There 61 | /// is no error detection but in some cases an empty name may be returned, 62 | /// e.g. for input like Foo."Hello". 63 | public static Symbol KeyNameComponentOf(LNode name) 64 | { 65 | if (name == null) 66 | return null; 67 | // global::Foo.Bar is structured (((global::Foo)).Bar) 68 | // So if #of, get first arg (which cannot itself be #of), then if #dot, 69 | // get second arg. 70 | if (name.CallsMin(S.Of, 1)) 71 | name = name.Args[0]; 72 | if (name.CallsMin(S.Dot, 1)) 73 | name = name.Args.Last; 74 | if (name.IsCall) 75 | return KeyNameComponentOf(name.Target); 76 | return name.Name; 77 | } 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /EnhancedC#Parser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CalcExample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CalcExample")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b5da2d90-b3e7-4b10-a486-e6e8c5a26441")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EnhancedC#Parser/TokenType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc.Syntax.Lexing; 6 | using System.Diagnostics; 7 | 8 | namespace Loyc.Ecs.Parser 9 | { 10 | using TT = TokenType; 11 | using Loyc; 12 | using Loyc.Syntax.Les; 13 | 14 | public enum TokenType 15 | { 16 | EOF = 0, 17 | Spaces = TokenKind.Spaces + 1, 18 | Newline = TokenKind.Spaces + 2, 19 | SLComment = TokenKind.Comment, 20 | MLComment = TokenKind.Comment + 1, 21 | Shebang = TokenKind.Comment + 2, 22 | Id = TokenKind.Id, 23 | // var, dynamic, trait, alias, where, assembly, module. 24 | // Does not include partial, because any Id can be a word attribute. 25 | ContextualKeyword = TokenKind.Id + 1, 26 | Base = TokenKind.Id + 2, 27 | This = TokenKind.Id + 3, 28 | Literal = TokenKind.Literal, 29 | Comma = TokenKind.Separator, 30 | Semicolon = TokenKind.Separator + 1, 31 | LParen = TokenKind.LParen, 32 | RParen = TokenKind.RParen, 33 | LBrack = TokenKind.LBrack, 34 | RBrack = TokenKind.RBrack, 35 | LBrace = TokenKind.LBrace, 36 | RBrace = TokenKind.RBrace, 37 | AttrKeyword = TokenKind.AttrKeyword, 38 | TypeKeyword = TokenKind.TypeKeyword, 39 | 40 | Break = TokenKind.OtherKeyword + 1, 41 | Case = TokenKind.OtherKeyword + 2, 42 | Checked = TokenKind.OtherKeyword + 3, 43 | Class = TokenKind.OtherKeyword + 4, 44 | Continue = TokenKind.OtherKeyword + 5, 45 | Default = TokenKind.OtherKeyword + 6, 46 | Delegate = TokenKind.OtherKeyword + 7, 47 | Do = TokenKind.OtherKeyword + 8, 48 | Enum = TokenKind.OtherKeyword + 9, 49 | Event = TokenKind.OtherKeyword + 10, 50 | Fixed = TokenKind.OtherKeyword + 11, 51 | For = TokenKind.OtherKeyword + 12, 52 | Foreach = TokenKind.OtherKeyword + 13, 53 | Goto = TokenKind.OtherKeyword + 14, 54 | If = TokenKind.OtherKeyword + 15, 55 | Interface = TokenKind.OtherKeyword + 16, 56 | Lock = TokenKind.OtherKeyword + 17, 57 | Namespace = TokenKind.OtherKeyword + 18, 58 | Return = TokenKind.OtherKeyword + 19, 59 | Struct = TokenKind.OtherKeyword + 20, 60 | Switch = TokenKind.OtherKeyword + 21, 61 | Throw = TokenKind.OtherKeyword + 22, 62 | Try = TokenKind.OtherKeyword + 23, 63 | Unchecked = TokenKind.OtherKeyword + 24, 64 | Using = TokenKind.OtherKeyword + 25, 65 | While = TokenKind.OtherKeyword + 26, 66 | 67 | Operator = TokenKind.OtherKeyword + 32, 68 | Sizeof = TokenKind.OtherKeyword + 33, 69 | Typeof = TokenKind.OtherKeyword + 34, 70 | 71 | Else = TokenKind.OtherKeyword + 40, 72 | Catch = TokenKind.OtherKeyword + 41, 73 | Finally = TokenKind.OtherKeyword + 42, 74 | 75 | In = TokenKind.OtherKeyword + 48, 76 | As = TokenKind.OtherKeyword + 49, 77 | Is = TokenKind.OtherKeyword + 50, 78 | 79 | New = TokenKind.OtherKeyword + 56, 80 | Out = TokenKind.OtherKeyword + 57, 81 | Stackalloc = TokenKind.OtherKeyword + 58, 82 | 83 | PPif = TokenKind.Other + 64, 84 | PPelse = TokenKind.Other + 65, 85 | PPelif = TokenKind.Other + 66, 86 | PPendif = TokenKind.Other + 67, 87 | PPdefine = TokenKind.Other + 68, 88 | PPundef = TokenKind.Other + 69, 89 | PPwarning = TokenKind.Other + 70, 90 | PPerror = TokenKind.Other + 71, 91 | PPnote = TokenKind.Other + 72, 92 | PPline = TokenKind.Other + 73, 93 | PPregion = TokenKind.Other + 74, 94 | PPendregion = TokenKind.Other + 75, 95 | PPpragma = TokenKind.Other + 76, 96 | PPignored = TokenKind.Other + 77, // covers one or more lines ignored by #if/#elif/#else. 97 | 98 | Dot = TokenKind.Dot, // . 99 | PtrArrow = TokenKind.Dot + 1, // -> 100 | ColonColon = TokenKind.Dot + 2, // :: 101 | NullDot = TokenKind.Dot + 3, // ?. 102 | 103 | Set = TokenKind.Assignment, // = 104 | CompoundSet = TokenKind.Assignment + 1, // +=, *=, >>=, etc. 105 | QuickBindSet = TokenKind.Assignment + 2, // := 106 | 107 | // Operators: Different operators that are used in the same way and have 108 | // the same precence may be grouped into a single TokenType. There is 109 | // no token type for >> or << because these are formed from two > or < 110 | // tokens. 111 | Colon = TokenKind.Operator, // : 112 | At = TokenKind.Operator + 1, // @ 113 | BQString = TokenKind.Operator + 2, // `...` 114 | Backslash = TokenKind.Operator + 4, // \ 115 | Mul = TokenKind.Operator + 5, // * 116 | DivMod = TokenKind.Operator + 6, // / % 117 | Power = TokenKind.Operator + 7, // ** (can also represent double-deref: (**x)) 118 | Add = TokenKind.Operator + 8, // + 119 | Sub = TokenKind.Operator + 9, // - 120 | IncDec = TokenKind.Operator + 10, // ++ -- 121 | And = TokenKind.Operator + 11, // && 122 | OrXor = TokenKind.Operator + 12, // || ^^ 123 | Not = TokenKind.Operator + 14, // ! 124 | AndBits = TokenKind.Operator + 15, // & 125 | OrBits = TokenKind.Operator + 16, // | 126 | XorBits = TokenKind.Operator + 17, // ^ 127 | NotBits = TokenKind.Operator + 18, // ~ 128 | EqNeq = TokenKind.Operator + 19, // == != 129 | LT = TokenKind.Operator + 20, // < 130 | GT = TokenKind.Operator + 21, // > 131 | LEGE = TokenKind.Operator + 22, // <= >= 132 | DotDot = TokenKind.Operator + 23, // .. 133 | QuestionMark = TokenKind.Operator + 24, // ? 134 | NullCoalesce = TokenKind.Operator + 25, // ?? 135 | QuickBind = TokenKind.Operator + 26, // =: 136 | Forward = TokenKind.Operator + 27, // ==> 137 | Substitute = TokenKind.Operator + 28, // $ 138 | LambdaArrow = TokenKind.Operator + 29, // => 139 | 140 | Unknown = TokenKind.Other, // invalid input 141 | //Indent = TokenKind.LBrace + 1, 142 | //Dedent = TokenKind.RBrace + 1, 143 | } 144 | 145 | /// Provides the Type() extension method required by 146 | /// and the ToString(Token) method to express an EC# token 147 | /// as a string, for tokens that contain sufficient information to do so. 148 | public static class TokenExt 149 | { 150 | /// Converts t.TypeInt to . 151 | [DebuggerStepThrough] 152 | public static TokenType Type(this Token t) { return (TokenType)t.TypeInt; } 153 | 154 | /// Expresses an EC# token as a string. 155 | /// This code is incomplete. 156 | /// 157 | /// Note that some Tokens do not contain enough information to 158 | /// reconstruct a useful token string, e.g. comment tokens do not store the 159 | /// comment but merely contain the location of the comment in the source code. 160 | /// For performance reasons, a does not have a reference 161 | /// to its source file, so this method cannot return the original string. 162 | /// 163 | public static string ToString(Token t) 164 | { 165 | StringBuilder sb = new StringBuilder(); 166 | if (t.Kind == TokenKind.Operator || t.Kind == TokenKind.Assignment || t.Kind == TokenKind.Dot) { 167 | if (t.Type() == TT.BQString) 168 | return Loyc.Syntax.Les.LesNodePrinter.PrintString((t.Value ?? "").ToString(), '`', false); 169 | string value = (t.Value ?? "(null)").ToString(); 170 | return value; 171 | } 172 | switch (t.Type()) { 173 | case TT.EOF: return "/*EOF*/"; 174 | case TT.Spaces: return " "; 175 | case TT.Newline: return "\n"; 176 | case TT.SLComment: return "//\n"; 177 | case TT.MLComment: return "/**/"; 178 | case TT.Shebang: return "#!" + (t.Value ?? "").ToString() + "\n"; 179 | case TT.Id: 180 | case TT.ContextualKeyword: 181 | return LesNodePrinter.PrintId(t.Value as Symbol ?? GSymbol.Empty); 182 | case TT.Base: return "base"; 183 | case TT.This: return "this"; 184 | case TT.Literal: 185 | return LesNodePrinter.PrintLiteral(t.Value, t.Style); 186 | case TT.Comma: return ","; 187 | case TT.Semicolon: return ";"; 188 | case TT.LParen: return "("; 189 | case TT.RParen: return ")"; 190 | case TT.LBrack: return "["; 191 | case TT.RBrack: return "]"; 192 | case TT.LBrace: return "{"; 193 | case TT.RBrace: return "}"; 194 | case TT.AttrKeyword: 195 | string value = (t.Value ?? "(null)").ToString(); 196 | return value; 197 | case TT.TypeKeyword: 198 | Symbol valueSym = (t.Value as Symbol) ?? GSymbol.Empty; 199 | return (t.Value ?? "(null)").ToString(); 200 | case TT.Break: return "break"; 201 | case TT.Case: return "case"; 202 | case TT.Checked: return "checked"; 203 | case TT.Class: return "class"; 204 | case TT.Continue: return "continue"; 205 | case TT.Default: return "default"; 206 | case TT.Delegate: return "delegate"; 207 | case TT.Do: return "do"; 208 | case TT.Enum: return "enum"; 209 | case TT.Event: return "event"; 210 | case TT.Fixed: return "fixed"; 211 | case TT.For: return "for"; 212 | case TT.Foreach: return "foreach"; 213 | case TT.Goto: return "goto"; 214 | case TT.If: return "if"; 215 | case TT.Interface: return "interface"; 216 | case TT.Lock: return "lock"; 217 | case TT.Namespace: return "namespace"; 218 | case TT.Return: return "return"; 219 | case TT.Struct: return "struct"; 220 | case TT.Switch: return "switch"; 221 | case TT.Throw: return "throw"; 222 | case TT.Try: return "try"; 223 | case TT.Unchecked: return "unchecked"; 224 | case TT.Using: return "using"; 225 | case TT.While: return "while"; 226 | 227 | case TT.Operator: return "operator"; 228 | case TT.Sizeof: return "sizeof"; 229 | case TT.Typeof: return "typeof"; 230 | case TT.Else: return "else"; 231 | case TT.Catch: return "catch"; 232 | case TT.Finally: return "finally"; 233 | case TT.In: return "in"; 234 | case TT.As: return "as"; 235 | case TT.Is: return "is"; 236 | case TT.New: return "new"; 237 | case TT.Out: return "out"; 238 | case TT.Stackalloc: return "stackalloc"; 239 | 240 | case TT.PPif: return "#if"; 241 | case TT.PPelse: return "#else"; 242 | case TT.PPelif: return "#elif"; 243 | case TT.PPendif: return "#endif"; 244 | case TT.PPdefine: return "#define"; 245 | case TT.PPundef: return "#undef"; 246 | case TT.PPwarning: return "#warning" + t.Value; 247 | case TT.PPerror: return "#error" + t.Value; 248 | case TT.PPnote: return "#note" + t.Value; 249 | case TT.PPline: return "#line"; 250 | case TT.PPregion: return "#region" + t.Value; 251 | case TT.PPendregion: return "#endregion"; 252 | case TT.PPpragma: return "#pragma"; 253 | case TT.PPignored: return (t.Value ?? "").ToString(); 254 | default: 255 | return string.Format("@`unknown token 0x{0:X4}`", t.TypeInt); 256 | } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /EnhancedC#Parser/TryEcsParser.NET40.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {245A0392-2D46-42DE-A2E6-42A5454DE2F2} 9 | Exe 10 | Properties 11 | Ecs.Parser 12 | TryEcsParser 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Collections.dll 39 | True 40 | 41 | 42 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Essentials.dll 43 | True 44 | 45 | 46 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Syntax.dll 47 | True 48 | 49 | 50 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Utilities.dll 51 | True 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | True 61 | True 62 | EcsLexerGrammar.les 63 | 64 | 65 | 66 | True 67 | True 68 | EcsParserGrammar.les 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | LLLPG 79 | EcsLexerGrammar.out.cs 80 | 81 | 82 | LLLPG 83 | EcsParserGrammar.out.cs 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 99 | -------------------------------------------------------------------------------- /EnhancedC#Parser/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /JsonExample/JsonExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {93A24B8C-C195-45B7-BF6B-7F01622D83F7} 9 | Exe 10 | Properties 11 | Json 12 | JsonExample 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Collections.dll 39 | True 40 | 41 | 42 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Essentials.dll 43 | True 44 | 45 | 46 | ..\packages\LoycCore.1.8.0\lib\net40-client\Loyc.Syntax.dll 47 | True 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | LLLPG 56 | JsonParser.out.cs 57 | 58 | 59 | True 60 | True 61 | JsonParser.ecs 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /JsonExample/JsonParser.ecs: -------------------------------------------------------------------------------- 1 | // JSON is a language so simple that we don't need a separate lexer and parser; 2 | // the entire parsing job can be done with just an LL(1) lexer. 3 | using System(, .Collections.Generic, .Linq, .Text, .Diagnostics); 4 | using Loyc(, .Collections, .Syntax(, .Lexing)); 5 | 6 | namespace Json 7 | { 8 | public class JsonParser : BaseLexer 9 | { 10 | /// Parses a json string into an object. 11 | /// a Dictionary{string, object}, List{object}, string or number. 12 | /// Remember, string converts implicitly to UString which boxes into ICharSource 13 | public static object Parse(UString chars, bool allowComments = false) { return Parse(chars, "", 0, true, allowComments); } 14 | public static object Parse(ICharSource chars, string fileName, int inputPosition = 0, 15 | bool checkForEofAfter = true, bool allowComments = false, IMessageSink errSink = null) 16 | { 17 | var parser = new JsonParser(chars, fileName, inputPosition, false) { AllowComments = allowComments }; 18 | if (errSink != null) parser.ErrorSink = errSink; 19 | var result = parser.Value(); 20 | if (checkForEofAfter && parser.LA0 != -1) 21 | parser.Error(0, "Expected EOF after JSON value"); 22 | return result; 23 | } 24 | 25 | public bool AllowComments { get; set; } 26 | public JsonParser(ICharSource chars, string fileName = "", int inputPosition = 0, bool newSourceFile = true) 27 | : base(chars, fileName, inputPosition, newSourceFile) {} 28 | 29 | [LL(1), FullLLk, AddCsLineDirectives(false)] 30 | LLLPG (lexer) 31 | @{ // '@' enables ANTLR syntax mode (not quite ANTLR-compatible though) 32 | // Whitespace ----------------------------------------------------- 33 | 34 | [LL(2)] // for comments 35 | SkipWS : 36 | greedy 37 | [ (' ' | '\t') 38 | | Newline 39 | | &{AllowComments} // not a real part of JSON 40 | ( "//" ~('\r'|'\n')* (Newline|EOF) 41 | | "/*" nongreedy(Newline / _)* "*/" ) 42 | ]*; 43 | extern rule Newline : '\r' + '\n'? | '\n'; // inherit from base class 44 | 45 | // Numbers & Strings ---------------------------------------------- 46 | 47 | private Number returns [double result] : {int start = InputPosition;} 48 | '-'? 49 | ( '0' | '1'..'9' '0'..'9'* ) 50 | ( '.' '0'..'9'* )? 51 | ( ('e'|'E') ('+'|'-')? '0'..'9'+ )? 52 | { 53 | UString str = CharSource.Slice(start, InputPosition - start); 54 | $result = ParseHelpers.TryParseDouble(ref str, 10); 55 | }; 56 | 57 | private String returns [string result] : {int start = InputPosition;} 58 | {bool escaped = false;} 59 | '"' 60 | ( '\\' _ {escaped = true;} | default ~('"'|'\\'|0..31) | error {Error(0, "Control character not permitted");} _ )* 61 | ('"' | error { Error(0, "Expected closing quote"); }) 62 | { 63 | UString text = CharSource.Slice(start + 1, InputPosition - start - 2); 64 | if (escaped) { 65 | $result = ParseHelpers.UnescapeCStyle(text); 66 | } else { 67 | $result = (string) text; 68 | } 69 | }; 70 | 71 | // Complex values ------------------------------------------------- 72 | 73 | protected Value returns [object result] : 74 | SkipWS 75 | ( result=Dictionary 76 | | result=List 77 | | result=Number 78 | | result=String 79 | | result=WordLiteral 80 | | error { Error(0, "Expected a value"); $result = null; } 81 | greedy(~('}'|']'|','))* 82 | ) SkipWS; 83 | 84 | [LL(10)] // LL(1) would work but not show the desired error message in case of e.g. "troo" 85 | private WordLiteral returns [object result] : {int start = InputPosition;} 86 | ( "true" {$result = G.BoxedTrue;} 87 | / "false" {return G.BoxedFalse;} 88 | / "null" {return null;} 89 | / ('a'..'z'|'A'..'Z')+ { 90 | Error(0, "JSON does not support identifiers"); 91 | return CharSource.Slice(start, InputPosition - start).ToString(); 92 | } 93 | ); 94 | 95 | private List returns [List result] : 96 | {$result = new List();} 97 | '[' SkipWS ( result+=Value (',' result+=Value)* )? ']'; 98 | 99 | private Dictionary returns [Dictionary result] : 100 | {$result = new Dictionary();} 101 | '{' SkipWS ( Pair[result] ( ',' Pair[result] )* )? '}'; 102 | 103 | private Pair[Dictionary dict] : 104 | ( SkipWS String SkipWS 105 | ( ':' Value 106 | {dict.Add($String, $Value);} 107 | | error { Error(0, "Expected value for '{0}'", $String); } 108 | ) 109 | | error { $String = ""; Error(0, "Expected a string key"); } ~(':'|'}'|',') 110 | ); 111 | }; 112 | } 113 | } -------------------------------------------------------------------------------- /JsonExample/JsonParser.out.cs: -------------------------------------------------------------------------------- 1 | // Generated from JsonParser.ecs by LeMP custom tool. LeMP version: 1.8.0.0 2 | // Note: you can give command-line arguments to the tool via 'Custom Tool Namespace': 3 | // --no-out-header Suppress this message 4 | // --verbose Allow verbose messages (shown by VS as 'warnings') 5 | // --timeout=X Abort processing thread after X seconds (default: 10) 6 | // --macros=FileName.dll Load macros from FileName.dll, path relative to this file 7 | // Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG); 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Diagnostics; 13 | using Loyc; 14 | using Loyc.Collections; 15 | using Loyc.Syntax; 16 | using Loyc.Syntax.Lexing; 17 | namespace Json 18 | { 19 | public class JsonParser : BaseLexer 20 | { 21 | public static object Parse(UString chars, bool allowComments = false) 22 | { 23 | return Parse(chars, "", 0, true, allowComments); 24 | } 25 | public static object Parse(ICharSource chars, string fileName, int inputPosition = 0, bool checkForEofAfter = true, bool allowComments = false, IMessageSink errSink = null) 26 | { 27 | var parser = new JsonParser(chars, fileName, inputPosition, false) { 28 | AllowComments = allowComments 29 | }; 30 | if (errSink != null) 31 | parser.ErrorSink = errSink; 32 | var result = parser.Value(); 33 | if (checkForEofAfter && parser.LA0 != -1) 34 | parser.Error(0, "Expected EOF after JSON value"); 35 | return result; 36 | } 37 | public bool AllowComments 38 | { 39 | get; 40 | set; 41 | } 42 | public JsonParser(ICharSource chars, string fileName = "", int inputPosition = 0, bool newSourceFile = true) : base(chars, fileName, inputPosition, newSourceFile) 43 | { 44 | } 45 | void SkipWS() 46 | { 47 | int la0, la1; 48 | // Line 37: greedy( [\t ] | Newline | &{AllowComments} ([/] [/] ([^\$\n\r])* (Newline | [\$]) | [/] [*] nongreedy(Newline / [^\$])* [*] [/]) )* 49 | for (;;) { 50 | switch (LA0) { 51 | case '\t': 52 | case ' ': 53 | Skip(); 54 | break; 55 | case '\n': 56 | case '\r': 57 | Newline(); 58 | break; 59 | case '/': 60 | { 61 | la1 = LA(1); 62 | if (la1 == '*' || la1 == '/') { 63 | Check(AllowComments, "AllowComments"); 64 | // Line 40: ([/] [/] ([^\$\n\r])* (Newline | [\$]) | [/] [*] nongreedy(Newline / [^\$])* [*] [/]) 65 | la1 = LA(1); 66 | if (la1 == '/') { 67 | Match('/'); 68 | Skip(); 69 | // Line 40: ([^\$\n\r])* 70 | for (;;) { 71 | la0 = LA0; 72 | if (!(la0 == -1 || la0 == '\n' || la0 == '\r')) 73 | Skip(); 74 | else 75 | break; 76 | } 77 | // Line 40: (Newline | [\$]) 78 | la0 = LA0; 79 | if (la0 == '\n' || la0 == '\r') 80 | Newline(); 81 | else 82 | Match(-1); 83 | } else { 84 | Match('/'); 85 | Match('*'); 86 | // Line 41: nongreedy(Newline / [^\$])* 87 | for (;;) { 88 | switch (LA0) { 89 | case '*': 90 | { 91 | la1 = LA(1); 92 | if (la1 == -1 || la1 == '/') 93 | goto stop; 94 | else 95 | Skip(); 96 | } 97 | break; 98 | case -1: 99 | goto stop; 100 | case '\n': 101 | case '\r': 102 | Newline(); 103 | break; 104 | default: 105 | Skip(); 106 | break; 107 | } 108 | } 109 | stop:; 110 | Match('*'); 111 | Match('/'); 112 | } 113 | } else 114 | goto stop2; 115 | } 116 | break; 117 | default: 118 | goto stop2; 119 | } 120 | } 121 | stop2:; 122 | } 123 | double Number() 124 | { 125 | int la0; 126 | double result = default(double); 127 | // line 47 128 | int start = InputPosition; 129 | // Line 48: ([\-])? 130 | la0 = LA0; 131 | if (la0 == '-') 132 | Skip(); 133 | // Line 49: ([0] | [1-9] ([0-9])*) 134 | la0 = LA0; 135 | if (la0 == '0') 136 | Skip(); 137 | else { 138 | MatchRange('1', '9'); 139 | // Line 49: ([0-9])* 140 | for (;;) { 141 | la0 = LA0; 142 | if (la0 >= '0' && la0 <= '9') 143 | Skip(); 144 | else 145 | break; 146 | } 147 | } 148 | // Line 50: ([.] ([0-9])*)? 149 | la0 = LA0; 150 | if (la0 == '.') { 151 | Skip(); 152 | // Line 50: ([0-9])* 153 | for (;;) { 154 | la0 = LA0; 155 | if (la0 >= '0' && la0 <= '9') 156 | Skip(); 157 | else 158 | break; 159 | } 160 | } 161 | // Line 51: ([Ee] ([+\-])? [0-9] ([0-9])*)? 162 | la0 = LA0; 163 | if (la0 == 'E' || la0 == 'e') { 164 | Skip(); 165 | // Line 51: ([+\-])? 166 | la0 = LA0; 167 | if (la0 == '+' || la0 == '-') 168 | Skip(); 169 | MatchRange('0', '9'); 170 | // Line 51: ([0-9])* 171 | for (;;) { 172 | la0 = LA0; 173 | if (la0 >= '0' && la0 <= '9') 174 | Skip(); 175 | else 176 | break; 177 | } 178 | } 179 | // line 53 180 | UString str = CharSource.Slice(start, InputPosition - start); 181 | result = ParseHelpers.TryParseDouble(ref str, 10); 182 | return result; 183 | } 184 | string String() 185 | { 186 | int la0; 187 | string result = default(string); 188 | int start = InputPosition; 189 | bool escaped = false; 190 | Match('"'); 191 | // Line 60: ([\\] [^\$] | default [^\$-\x1F"\\])* 192 | for (;;) { 193 | la0 = LA0; 194 | if (la0 == '\\') { 195 | Skip(); 196 | MatchExcept(); 197 | // line 60 198 | escaped = true; 199 | } else if (!(la0 >= -1 && la0 <= '\x1F' || la0 == '"')) 200 | Skip(); 201 | else if (la0 == -1 || la0 == '"') 202 | break; 203 | else { 204 | // line 60 205 | Error(0, "Control character not permitted"); 206 | MatchExcept(); 207 | } 208 | } 209 | // Line 61: (["]) 210 | la0 = LA0; 211 | if (la0 == '"') 212 | Skip(); 213 | else { 214 | // line 61 215 | Error(0, "Expected closing quote"); 216 | } 217 | // line 63 218 | UString text = CharSource.Slice(start + 1, InputPosition - start - 2); 219 | if (escaped) { 220 | result = ParseHelpers.UnescapeCStyle(text); 221 | } else { 222 | result = (string) text; 223 | } 224 | return result; 225 | } 226 | protected object Value() 227 | { 228 | int la0; 229 | object result = default(object); 230 | SkipWS(); 231 | // Line 75: ( Dictionary | List | Number | String | WordLiteral ) 232 | la0 = LA0; 233 | if (la0 == '{') 234 | result = Dictionary(); 235 | else if (la0 == '[') 236 | result = List(); 237 | else if (la0 == '-' || la0 >= '0' && la0 <= '9') 238 | result = Number(); 239 | else if (la0 == '"') 240 | result = String(); 241 | else if (la0 >= 'A' && la0 <= 'Z' || la0 >= 'a' && la0 <= 'z') 242 | result = WordLiteral(); 243 | else { 244 | // line 80 245 | Error(0, "Expected a value"); 246 | result = null; 247 | // Line 81: greedy([^\$,\]}])* 248 | for (;;) { 249 | la0 = LA0; 250 | if (!(la0 == -1 || la0 == ',' || la0 == ']' || la0 == '}')) 251 | Skip(); 252 | else 253 | break; 254 | } 255 | } 256 | SkipWS(); 257 | return result; 258 | } 259 | object WordLiteral() 260 | { 261 | int la0, la1, la2, la3, la4; 262 | object result = default(object); 263 | int start = InputPosition; 264 | // Line 86: ( [t] [r] [u] [e] / [f] [a] [l] [s] [e] / [n] [u] [l] [l] / [A-Za-z] ([A-Za-z])* ) 265 | do { 266 | la0 = LA0; 267 | if (la0 == 't') { 268 | la1 = LA(1); 269 | if (la1 == 'r') { 270 | la2 = LA(2); 271 | if (la2 == 'u') { 272 | la3 = LA(3); 273 | if (la3 == 'e') { 274 | switch (LA(4)) { 275 | case -1: 276 | case '\t': 277 | case '\n': 278 | case '\r': 279 | case ' ': 280 | case ',': 281 | case '/': 282 | case ']': 283 | case '}': 284 | { 285 | Skip(); 286 | Skip(); 287 | Skip(); 288 | Skip(); 289 | // line 86 290 | result = G.BoxedTrue; 291 | } 292 | break; 293 | default: 294 | goto match4; 295 | } 296 | } else 297 | goto match4; 298 | } else 299 | goto match4; 300 | } else 301 | goto match4; 302 | } else if (la0 == 'f') { 303 | la1 = LA(1); 304 | if (la1 == 'a') { 305 | la2 = LA(2); 306 | if (la2 == 'l') { 307 | la3 = LA(3); 308 | if (la3 == 's') { 309 | la4 = LA(4); 310 | if (la4 == 'e') { 311 | switch (LA(5)) { 312 | case -1: 313 | case '\t': 314 | case '\n': 315 | case '\r': 316 | case ' ': 317 | case ',': 318 | case '/': 319 | case ']': 320 | case '}': 321 | { 322 | Skip(); 323 | Skip(); 324 | Skip(); 325 | Skip(); 326 | Skip(); 327 | // line 87 328 | return G.BoxedFalse; 329 | } 330 | default: 331 | goto match4; 332 | } 333 | } else 334 | goto match4; 335 | } else 336 | goto match4; 337 | } else 338 | goto match4; 339 | } else 340 | goto match4; 341 | } else if (la0 == 'n') { 342 | la1 = LA(1); 343 | if (la1 == 'u') { 344 | la2 = LA(2); 345 | if (la2 == 'l') { 346 | la3 = LA(3); 347 | if (la3 == 'l') { 348 | switch (LA(4)) { 349 | case -1: 350 | case '\t': 351 | case '\n': 352 | case '\r': 353 | case ' ': 354 | case ',': 355 | case '/': 356 | case ']': 357 | case '}': 358 | { 359 | Skip(); 360 | Skip(); 361 | Skip(); 362 | Skip(); 363 | // line 88 364 | return null; 365 | } 366 | default: 367 | goto match4; 368 | } 369 | } else 370 | goto match4; 371 | } else 372 | goto match4; 373 | } else 374 | goto match4; 375 | } else 376 | goto match4; 377 | break; 378 | match4: 379 | { 380 | MatchRange('A', 'Z', 'a', 'z'); 381 | // Line 89: ([A-Za-z])* 382 | for (;;) { 383 | la0 = LA0; 384 | if (la0 >= 'A' && la0 <= 'Z' || la0 >= 'a' && la0 <= 'z') 385 | Skip(); 386 | else 387 | break; 388 | } 389 | // line 90 390 | Error(0, "JSON does not support identifiers"); 391 | return CharSource.Slice(start, InputPosition - start).ToString(); 392 | } 393 | } while (false); 394 | return result; 395 | } 396 | static readonly HashSet List_set0 = NewSetOfRanges(9, 10, 13, 13, ' ', ' ', '"', '"', '-', '-', '/', '9', 'A', '[', 'a', '{'); 397 | List List() 398 | { 399 | int la0; 400 | List result = default(List); 401 | // line 96 402 | result = new List(); 403 | Skip(); 404 | SkipWS(); 405 | // Line 97: (Value ([,] Value)*)? 406 | la0 = LA0; 407 | if (List_set0.Contains(la0)) { 408 | result.Add(Value()); 409 | // Line 97: ([,] Value)* 410 | for (;;) { 411 | la0 = LA0; 412 | if (la0 == ',') { 413 | Skip(); 414 | result.Add(Value()); 415 | } else 416 | break; 417 | } 418 | } 419 | Match(']'); 420 | return result; 421 | } 422 | Dictionary Dictionary() 423 | { 424 | int la0; 425 | Dictionary result = default(Dictionary); 426 | // line 100 427 | result = new Dictionary(); 428 | Skip(); 429 | SkipWS(); 430 | // Line 101: (Pair ([,] Pair)*)? 431 | switch (LA0) { 432 | case '\t': 433 | case '\n': 434 | case '\r': 435 | case ' ': 436 | case '"': 437 | case '/': 438 | { 439 | Pair(result); 440 | // Line 101: ([,] Pair)* 441 | for (;;) { 442 | la0 = LA0; 443 | if (la0 == ',') { 444 | Skip(); 445 | Pair(result); 446 | } else 447 | break; 448 | } 449 | } 450 | break; 451 | } 452 | Match('}'); 453 | return result; 454 | } 455 | void Pair(Dictionary dict) 456 | { 457 | int la0; 458 | string got_String = default(string); 459 | object got_Value = default(object); 460 | // Line 104: (SkipWS String SkipWS ([:] Value)) 461 | switch (LA0) { 462 | case '\t': 463 | case '\n': 464 | case '\r': 465 | case ' ': 466 | case '"': 467 | case '/': 468 | { 469 | SkipWS(); 470 | got_String = String(); 471 | SkipWS(); 472 | // Line 105: ([:] Value) 473 | la0 = LA0; 474 | if (la0 == ':') { 475 | Skip(); 476 | got_Value = Value(); 477 | // line 106 478 | dict.Add(got_String, got_Value); 479 | } else { 480 | // line 107 481 | Error(0, "Expected value for '{0}'", got_String); 482 | } 483 | } 484 | break; 485 | default: 486 | { 487 | // line 109 488 | got_String = ""; 489 | Error(0, "Expected a string key"); 490 | Skip(); 491 | } 492 | break; 493 | } 494 | } 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /JsonExample/JsonPrinter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics.Contracts; 6 | using Loyc.Collections; // for EscapeCStyle 7 | using Loyc.Syntax; 8 | using Loyc; // for EscapeCStyle 9 | 10 | namespace Json 11 | { 12 | /// A pretty-printer for printing "JSON-like" objects (sequences, 13 | /// dictionaries, strings and numbers). 14 | /// 15 | /// The main print function is , which returns 16 | /// the StringBuilder into which the output was placed. 17 | /// 18 | /// The only requirement to print a dictionary is that it implements 19 | /// IEnumerable{KeyValuePair{string,object}}, and to print a list it must 20 | /// implement IEnumerable{object}. A derived class can implement additional 21 | /// type conversion logic by overriding . 22 | /// 23 | /// This is actually quite a sophisticated printer in terms of how it handles 24 | /// newlines. 25 | /// - Indentation is used to show structure 26 | /// - It tries to keep lines under a certain width () 27 | /// - it packs small lists on a single line () 28 | /// - for large lists, up to 4 items are printed per line () 29 | /// 30 | public class JsonPrinter 31 | { 32 | protected PrinterState S; 33 | 34 | /// The JSON printer tries to keep the line width under this size. 35 | /// It can't always succeed - long strings or key-value pairs with long key 36 | /// strings may exceed this size. 37 | public int PreferredLineWidth { get; set; } 38 | /// Maximum number of items of a dictionary to pack on a single line. 39 | public int DictionaryGroupSize { get; set; } 40 | /// Maximum number of items of a list to pack on a single line. 41 | public int ListGroupSize { get; set; } 42 | /// Whether to print a newline after "{" or "[". 43 | public bool NewlineAfterOpener { get; set; } 44 | /// Maximum length of a list that is printed on a single line. 45 | /// Regardless of this setting, a list is broken across lines if 46 | /// it doesn't fit on the current line. 47 | public int MaxItemsInSingleLineList { get; set; } 48 | /// Maximum length of a dictionary that is printed on a single line 49 | public int MaxItemsInSingleLineDictionary { get; set; } 50 | /// Whether to print a space inside "[" and "]". 51 | public bool SpaceInsideSquareBrackets { get; set; } 52 | /// Whether to print a space inside "{" and "}". 53 | public bool SpaceInsideBraces { get; set; } 54 | /// StringBuilder to which output will be appended. 55 | public StringBuilder StringBuilder { get { return S.S; } set { S.S = value; } } 56 | 57 | /// Configures the printer object. 58 | /// Chooses which default settings to use. If this is 59 | /// true, the printer tries to pack more stuff on each line. 60 | /// Indent character(s) 61 | /// Newline character(s) 62 | /// A StringBuilder to which to append (if null, a new one is created) 63 | public JsonPrinter(bool compactMode = true, string indent = "\t", string newline = "\n", StringBuilder s = null) 64 | { 65 | S = new PrinterState(s ?? new StringBuilder(), indent, newline); 66 | PreferredLineWidth = 80; 67 | NewlineAfterOpener = true; 68 | SpaceInsideBraces = SpaceInsideSquareBrackets = !compactMode; 69 | if (compactMode) { 70 | DictionaryGroupSize = 2; 71 | ListGroupSize = 4; 72 | MaxItemsInSingleLineList = 8; 73 | MaxItemsInSingleLineDictionary = 4; 74 | } else { 75 | DictionaryGroupSize = 1; 76 | ListGroupSize = 1; 77 | MaxItemsInSingleLineList = 2; 78 | MaxItemsInSingleLineDictionary = 1; 79 | } 80 | } 81 | 82 | /// Prints an object in JSON format 83 | /// The StringBuilder with which this printer was initialized 84 | public virtual StringBuilder Print(object obj) 85 | { 86 | if (obj is ValueType) { 87 | PrintOther(obj); 88 | } else if (obj is string) { 89 | PrintString((string)obj); 90 | } else { 91 | var dict = obj as IEnumerable>; 92 | if (dict != null) { 93 | PrintDict(dict); 94 | } else { 95 | var list = obj as IEnumerable; 96 | if (list != null) 97 | PrintList(list); 98 | else 99 | PrintOther(obj); 100 | } 101 | } 102 | return S.S; 103 | } 104 | 105 | protected virtual void PrintOther(object obj) 106 | { 107 | if (obj == null) 108 | S.Append("null"); 109 | else if (obj is bool) 110 | S.Append((bool)obj ? "true" : "false"); 111 | else if (obj is UString) 112 | PrintString((UString)obj); 113 | else 114 | S.Append(obj.ToString()); 115 | } 116 | 117 | public void PrintString(UString str) 118 | { 119 | int oldLen = S.S.Length; 120 | S.Append("\""); 121 | S.Append(ParseHelpers.EscapeCStyle(str, EscapeC.Minimal | EscapeC.DoubleQuotes | EscapeC.ABFV | EscapeC.Control)); 122 | S.Append("\""); 123 | } 124 | 125 | public void PrintList(IEnumerable obj) 126 | { 127 | PrintListOrDictionary(obj, "[", "]", SpaceInsideSquareBrackets, MaxItemsInSingleLineList, item => Print(item)); 128 | } 129 | 130 | public void PrintDict(IEnumerable> obj) 131 | { 132 | PrintListOrDictionary(obj, "{", "}", SpaceInsideBraces, MaxItemsInSingleLineDictionary, pair => 133 | { 134 | PrintString(pair.Key); 135 | S.Append(": "); 136 | if (pair.Value is string && pair.Key.Length > 2) { 137 | // Key and value may be too long for one line; try two 138 | var cp = S.Newline(+1); 139 | PrintString(pair.Value as string); 140 | S.Dedent(); 141 | S.RevokeOrCommitNewlines(cp, PreferredLineWidth); 142 | } else 143 | Print(pair.Value); 144 | }); 145 | } 146 | 147 | #region Helper functions 148 | 149 | protected void PrintListOrDictionary(IEnumerable obj, string opener, string closer, bool spaceInside, int maxItemsInSingleLineList, Action printItem) 150 | { 151 | var cp = S.GetCheckpoint(); 152 | PrintOpener(opener, spaceInside); 153 | int count; 154 | PrintSequence(obj.GetEnumerator(), ListGroupSize, out count, printItem); 155 | PrintCloser(closer, spaceInside); 156 | // If the list is short enough, remove associated newlines so it fits on one line 157 | S.RevokeOrCommitNewlines(cp, count <= maxItemsInSingleLineList ? PreferredLineWidth : 0); 158 | } 159 | 160 | protected void PrintOpener(string bracket, bool space) 161 | { 162 | S.Append(bracket); 163 | if (space) S.Append(" "); 164 | if (NewlineAfterOpener) 165 | S.Newline(+1); 166 | } 167 | 168 | protected void PrintCloser(string bracket, bool space) 169 | { 170 | if (space) S.Append(" "); 171 | S.Newline(-1); 172 | S.Append(bracket); 173 | } 174 | 175 | // Prints a sequence with multiple items on each line. 176 | // Returns the length of the sequence in characters. 177 | protected void PrintSequence(IEnumerator e, int maxPerLine, out int count, Action printItem, string separator = ", ") 178 | { 179 | Contract.Assert(maxPerLine > 0); 180 | count = 0; 181 | if (!e.MoveNext()) 182 | return; 183 | for (;;) { 184 | int preferredLineWidth = PreferredLineWidth; 185 | 186 | // First item in group 187 | var checkpoint = S.GetCheckpoint(); 188 | printItem(e.Current); 189 | count++; 190 | if (S.LineNo != checkpoint.LineNo) 191 | preferredLineWidth = 0; 192 | 193 | // Rest of items in the current group 194 | for (int i = 1; i < maxPerLine; i++) { 195 | if (!e.MoveNext()) 196 | return; 197 | 198 | S.Append(separator); 199 | checkpoint = S.Newline(); 200 | printItem(e.Current); 201 | count++; 202 | if (S.RevokeOrCommitNewlines(checkpoint, preferredLineWidth) >= 0) 203 | preferredLineWidth = 0; 204 | } 205 | 206 | if (!e.MoveNext()) 207 | return; 208 | 209 | S.Append(separator); 210 | S.Newline(); 211 | } 212 | } 213 | 214 | #endregion 215 | } 216 | } -------------------------------------------------------------------------------- /JsonExample/PrinterState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics.Contracts; 6 | using System.Diagnostics; 7 | using Loyc.Collections.Impl; 8 | 9 | namespace Json 10 | { 11 | /// A helper type for printer objects. Its primary purposes are to 12 | /// manage indentation and to "revoke" newlines; it also tracks the current 13 | /// line/column number. 14 | /// 15 | /// Be careful not to duplicate this structure. 16 | /// 17 | /// When pretty-printing any language as text, it's a challenge to decide 18 | /// where to place newlines. You may want to break up long lines into 19 | /// shorter ones, as in 20 | ///
 21 | 	/// if (ReallyLongIdentifier[Fully.Qualified.Name(multiple, parameters)] 
 22 | 	///    > SomeConstant)
 23 | 	/// {
 24 | 	///    return ReallyLongIdentifier[firstThing + secondThing] 
 25 | 	///       + thirdThing + fourthThing;
 26 | 	/// }
 27 | 	/// 
28 | /// Conversely, you may want to print something on one line that you would 29 | /// ordinarily print on two: 30 | ///
 31 | 	///     if (c) break;
 32 | 	/// 
33 | /// Of course, the problem is, you don't know how long the syntax tree 34 | /// will be in text form until after you try to print it. 35 | /// 36 | /// My first idea to solve this problem was to use a 37 | /// rope 38 | /// tree data structure - inner syntax trees would produce small strings 39 | /// that could be "roped" together to produce a bigger tree. But ropes tend 40 | /// not to use memory efficiently, and there was the challenge, which I 41 | /// didn't see how to solve, was how to keep the tree balanced efficiently 42 | /// (for this particular application perhaps a balanced tree wasn't needed, 43 | /// but as a perfectionist I didn't want to implement a "half-baked" data 44 | /// structure.) 45 | /// 46 | /// Next I thought of the solution used here, a simpler solution based on an 47 | /// ordinary StringBuilder. My idea was to insert newlines "pessimistically" 48 | /// - insert them everywhere in which they might be needed - and then 49 | /// selectively "revoke" them later if they turn out to be unnecessary. Only 50 | /// the most recently-written newline(s) can be revoked, which keeps the 51 | /// implementation simple and also limits the performance cost of deleting 52 | /// the newlines. 53 | /// 54 | /// To use, call Newline() to write a newline (with indentation). To make 55 | /// a decision about whether to keep or revoke the most recent newline(s), 56 | /// call RevokeOrCommitNewlines(cp, maxLineLength) where cp is a "checkpoint" 57 | /// representing some point before the first newline want to potentially 58 | /// revoke, and maxLineLength is the line length threshold: if the line length 59 | /// after combining lines starting at the line on which the checkpoint is 60 | /// located does not exceed maxLineLength, then the newlines are revoked, 61 | /// otherwise ALL newlines are committed (so earlier newlines can no longer 62 | /// be revoked.) 63 | /// 64 | /// This design allows a potentially long series of newlines to be deleted 65 | /// in the reverse order that they were created, but if any newline is kept 66 | /// then previous ones can no longer be deleted. 67 | ///
68 | public struct PrinterState 69 | { 70 | public StringBuilder S; 71 | public int IndentLevel; 72 | public int LineNo; 73 | public string IndentString; 74 | public string NewlineString; 75 | private int _lineStartIndex; 76 | public int LineStartIndex { get { return _lineStartIndex; } } 77 | public int IndexInCurrentLine { get { return S.Length - _lineStartIndex; } } 78 | private InternalList _newlines; 79 | 80 | public PrinterState(StringBuilder s, string indent = "\t", string newline = "\n") 81 | { 82 | S = s ?? new StringBuilder(); 83 | IndentLevel = 0; 84 | IndentString = indent; 85 | NewlineString = newline; 86 | _lineStartIndex = 0; 87 | LineNo = 1; 88 | _newlines = InternalList.Empty; 89 | } 90 | 91 | public StringBuilder Append(string s) 92 | { 93 | return S.Append(s); 94 | } 95 | 96 | public void Indent() 97 | { 98 | IndentLevel++; 99 | } 100 | public void Dedent() 101 | { 102 | IndentLevel--; 103 | } 104 | 105 | /// Current length of the output string 106 | public int Length { get { return S.Length; } } 107 | 108 | public Checkpoint GetCheckpoint() { 109 | return new Checkpoint { _oldLineStart = _lineStartIndex, _oldLineNo = LineNo }; 110 | } 111 | 112 | /// Writes a newline and the appropriate amount of indentation afterward. 113 | /// Whether to call Indent() beore writing the newline 114 | /// A that can be used to revoke the newline 115 | /// Note that "revoking" a newline does NOT restore the original indent level. 116 | public Checkpoint Newline(int changeIndentLevel = 0) 117 | { 118 | var cp = GetCheckpoint(); 119 | IndentLevel += changeIndentLevel; 120 | Contract.Assert(IndentLevel >= 0); 121 | 122 | var r = new Revokable(_lineStartIndex, S.Length, NewlineString.Length + IndentString.Length * IndentLevel); 123 | S.Append(NewlineString); 124 | LineNo++; 125 | _lineStartIndex = S.Length; 126 | for (int i = 0; i < IndentLevel; i++) 127 | S.Append(IndentString); 128 | _newlines.Add(r); 129 | return cp; 130 | } 131 | 132 | /// Revokes or commits newlines added since the specified 133 | /// checkpoint. Recent newlines are revoked if the combined line length 134 | /// after revokation does not exceed maxLineWidth, otherwise ALL 135 | /// newlines are committed permanently. 136 | /// 0 if the method had no effect, -N if N newlines were 137 | /// revoked, and +N if N newlines were committed. 138 | /// This method does not affect the indent level. 139 | public int RevokeOrCommitNewlines(Checkpoint cp, int maxLineWidth) 140 | { 141 | // Length before revokation 142 | int lengthAfterCP = S.Length - cp._oldLineStart; 143 | // Figure out which newlines we can revoke and what the total line 144 | // length would be if they were revoked 145 | int newlinesCount = _newlines.Count; 146 | int i0; 147 | bool any = false; 148 | for (i0 = newlinesCount - 1; i0 >= 0 && _newlines[i0]._index >= cp._oldLineStart; i0--) { 149 | lengthAfterCP -= _newlines[i0].Length; 150 | any = true; 151 | } 152 | 153 | if (any) { 154 | if (lengthAfterCP <= maxLineWidth) { 155 | for (int i = newlinesCount - 1; i > i0; i--) { 156 | Revoke(_newlines[i]); 157 | _newlines.RemoveAt(i); 158 | } 159 | Debug.Assert(cp._oldLineNo == LineNo); 160 | Debug.Assert(cp._oldLineStart == _lineStartIndex); 161 | return -(newlinesCount - 1 - i0); 162 | } else { 163 | // We have decided not to revoke the newest newlines; this means 164 | // we can't revoke older ones later, since Revoke() does not 165 | // support revoking some recent newlines and not others. 166 | _newlines.Clear(); 167 | return newlinesCount; 168 | } 169 | } 170 | return 0; 171 | } 172 | 173 | /// Revokes (deletes) the last newline created, and its indent. 174 | /// Object returned from Newline() 175 | /// Only the most recent newline can be revoked, and of course, 176 | /// it can only be revoked once. Multiple newlines can be revoked if 177 | /// they are revoked in the reverse order in which they were created. 178 | private void Revoke(Revokable r) 179 | { 180 | S.Remove(r._index, r._length); 181 | _lineStartIndex = r._oldLineStartIndex; 182 | LineNo--; 183 | } 184 | 185 | private struct Revokable 186 | { 187 | internal int _index; 188 | internal int _length; 189 | internal int _oldLineStartIndex; 190 | public Revokable(int oldNewlineIndex, int index, int length) 191 | { 192 | _oldLineStartIndex = oldNewlineIndex; 193 | _index = index; 194 | _length = length; 195 | } 196 | public int Length { get { return _length; } } 197 | } 198 | 199 | public struct Checkpoint 200 | { 201 | internal int _oldLineStart; // at start of line 202 | internal int _oldLineNo; 203 | public int LineNo { get { return _oldLineNo; } } 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /JsonExample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Loyc; 6 | using Loyc.Collections; 7 | using Loyc.Syntax; 8 | using System.IO; 9 | using Loyc.Syntax.Lexing; 10 | using System.Diagnostics.Contracts; 11 | using System.Diagnostics; 12 | 13 | namespace Json 14 | { 15 | class Program 16 | { 17 | static void Main(string[] args) 18 | { 19 | RunMenu(new List>() 20 | { 21 | new Pair("Try example JSON strings", ParseAndPrintExample), 22 | new Pair("Try parsing '*.json' files", ParseJsonFiles), 23 | new Pair("Try console readline", ParseConsoleReadLine), 24 | }); 25 | } 26 | 27 | static void ParseAndPrintExample() 28 | { 29 | string result, roundTrip; 30 | object testPrint = new List { 31 | null, 1.0, 2.0, 32 | new Dictionary { 33 | { "three", 3 }, 34 | { "four", new List { 4.0, new List() } } 35 | }, 36 | 5 37 | }; 38 | Console.WriteLine("Printer test:"); 39 | Console.WriteLine(@" Expect: [null, 1, 2, { ""three"": 3, ""four"": [4, []] }, 5]"); 40 | try { 41 | result = Json.Print(testPrint, compactMode: true); 42 | Console.WriteLine(" Output: " + result); 43 | } catch (LogException ex) { 44 | ex.Msg.WriteTo(MessageSink.Console); 45 | } 46 | 47 | Console.WriteLine("Parser test:"); 48 | string testParse = @" [ 1, 2.0, { ""\three"" : 3.0e3 , ""four\n"" : [0.4e+1 , [ ] ] } , 40.04e-1 , { } ] "; 49 | Console.WriteLine(" Input: " + testParse); 50 | result = Json.Print(Json.Parse(testParse)); 51 | Console.WriteLine(" Result: " + result); 52 | roundTrip = Json.Print(Json.Parse(result)); 53 | Console.WriteLine("Roundtrip: " + roundTrip); 54 | } 55 | 56 | static void ParseJsonFiles() 57 | { 58 | int count = 0; 59 | foreach (string filename in Directory.EnumerateFiles("..", "*.json", SearchOption.AllDirectories)) { 60 | count++; 61 | Console.WriteLine(filename); 62 | 63 | // The printer uses '\t' by default which is 8 characters in console. Ask for two spaces. 64 | var printer = new JsonPrinter(true, " ", "\n"); 65 | using (var file = File.Open(filename, FileMode.Open, FileAccess.Read)) { 66 | var obj = Json.Parse(file, filename, false, MessageSink.Console); 67 | var str = printer.Print(obj).ToString(); 68 | Console.WriteLine(str); 69 | // Now make sure it round-trips fully 70 | printer.StringBuilder.Clear(); 71 | Loyc.MiniTest.Assert.AreEqual(str, printer.Print(Json.Parse(str)).ToString()); 72 | } 73 | } 74 | Console.WriteLine("--- Parsed & reserialized {0} files ---", count); 75 | } 76 | 77 | static void ParseConsoleReadLine() 78 | { 79 | Console.WriteLine("Please input a JSON string to parse:"); 80 | string line = Console.ReadLine(); 81 | string sparse = Json.Print(Json.Parse(line, false, MessageSink.Console), false); 82 | // Round trip again to make sure everything works 83 | Console.WriteLine(Json.Print(Json.Parse(sparse, false, MessageSink.Console), true)); 84 | Console.WriteLine(sparse); 85 | } 86 | 87 | public static void RunMenu(IList> menu) 88 | { 89 | for (;;) 90 | { 91 | Console.WriteLine(); 92 | Console.WriteLine("What do you want to do? (Esc to quit)"); 93 | for (int i = 0; i < menu.Count; i++) 94 | Console.WriteLine(ParseHelpers.HexDigitChar(i + 1) + ". " + menu[i].Key); 95 | 96 | ConsoleKeyInfo k; 97 | Console.WriteLine((k = Console.ReadKey(true)).KeyChar); 98 | if (k.Key == ConsoleKey.Escape || k.Key == ConsoleKey.Enter) 99 | break; 100 | else 101 | { 102 | int i = ParseHelpers.HexDigitValue(k.KeyChar); 103 | if (i > 0 && i <= menu.Count) 104 | menu[i - 1].Value(); 105 | } 106 | } 107 | } 108 | } 109 | 110 | /// A helper class for invoking JsonPrinter and JsonParser 111 | class Json 112 | { 113 | /// Parses a json string into an object. 114 | /// JSON data 115 | /// a Dictionary{string, object}, List{object}, string or number. 116 | public static object Parse(string json, bool allowComments = false, IMessageSink sink = null) 117 | { 118 | return JsonParser.Parse((UString)json, "", 0, true, allowComments, sink); 119 | } 120 | public static object Parse(Stream stream, string fileName = "", bool allowComments = false, IMessageSink errSink = null) 121 | { 122 | return JsonParser.Parse(new StreamCharSource(stream), fileName, 0, true, allowComments, errSink); 123 | } 124 | 125 | public static string Print(object obj, bool compactMode = true) 126 | { 127 | return new JsonPrinter(compactMode).Print(obj).ToString(); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /JsonExample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JsonExample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("JsonExample")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bb4ae186-d94f-4399-96aa-2bb85e2dd1d6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /JsonExample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | The demo source code (not including the EC# parser developed for Loyc and LLLPG)... 2 | 3 | - CalcExample-Standalone 4 | - CalcExample-UsingLoycLibs 5 | - CalcExample-UsingLoycTrees 6 | - JsonExample 7 | 8 | ...is hereby released into the public domain, or under the CC0 license where the concept of public domain is not recognized: 9 | 10 | http://creativecommons.org/publicdomain/zero/1.0/ 11 | 12 | Author: David Piepgrass a.k.a. Qwertie -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Loyc LL(k) Parser Generator (LLLPG) 2 | 3 | Articles about LLLPG are published on the [home page](http://ecsharp.net/lllpg/). 4 | 5 | This repo contains the following demos: 6 | 7 | - Boilerplate (a very simple two-stage parser - good starting point for your own parsers) 8 | - Calculator demo (standalone) 9 | - Calculator demo (based on Loyc libraries) 10 | - Calculator demo (produces [Loyc trees](https://github.com/qwertie/LoycCore/wiki/Loyc-trees)) 11 | - Enhanced C# parser (generally NOT kept up-to-date) 12 | - JSON parser and printer 13 | 14 | Please get LLLPG itself from [the ecsharp repo](https://github.com/qwertie/ecsharp/releases). See also: [installation instructions](http://ecsharp.net/lemp/install.html) 15 | -------------------------------------------------------------------------------- /Samples.NET40.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalcExample.NET40", "CalcExample-UsingLoycLibs\CalcExample.NET40.csproj", "{9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalcExample-Standalone.NET40", "CalcExample-Standalone\CalcExample-Standalone.NET40.csproj", "{03B6304E-FF66-4468-A7E0-92AA6E543E99}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalcExample-LoycTrees.NET40", "CalcExample-UsingLoycTrees\CalcExample-LoycTrees.NET40.csproj", "{DC8C2059-EC43-4A79-96CA-A90F86545BE8}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TryEcsParser.NET40", "EnhancedC#Parser\TryEcsParser.NET40.csproj", "{245A0392-2D46-42DE-A2E6-42A5454DE2F2}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonExample", "JsonExample\JsonExample.csproj", "{93A24B8C-C195-45B7-BF6B-7F01622D83F7}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boilerplate", "Boilerplate\Boilerplate.csproj", "{AA20B0B4-0773-49C5-AD11-D3C156E4B865}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|x86 = Debug|x86 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Debug|x86.ActiveCfg = Debug|x86 23 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Debug|x86.Build.0 = Debug|x86 24 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Release|x86.ActiveCfg = Release|x86 25 | {9D67B30F-E6D8-47AB-A1ED-B091B9AD6988}.Release|x86.Build.0 = Release|x86 26 | {03B6304E-FF66-4468-A7E0-92AA6E543E99}.Debug|x86.ActiveCfg = Debug|x86 27 | {03B6304E-FF66-4468-A7E0-92AA6E543E99}.Debug|x86.Build.0 = Debug|x86 28 | {03B6304E-FF66-4468-A7E0-92AA6E543E99}.Release|x86.ActiveCfg = Release|x86 29 | {03B6304E-FF66-4468-A7E0-92AA6E543E99}.Release|x86.Build.0 = Release|x86 30 | {DC8C2059-EC43-4A79-96CA-A90F86545BE8}.Debug|x86.ActiveCfg = Debug|x86 31 | {DC8C2059-EC43-4A79-96CA-A90F86545BE8}.Debug|x86.Build.0 = Debug|x86 32 | {DC8C2059-EC43-4A79-96CA-A90F86545BE8}.Release|x86.ActiveCfg = Release|x86 33 | {DC8C2059-EC43-4A79-96CA-A90F86545BE8}.Release|x86.Build.0 = Release|x86 34 | {245A0392-2D46-42DE-A2E6-42A5454DE2F2}.Debug|x86.ActiveCfg = Debug|x86 35 | {245A0392-2D46-42DE-A2E6-42A5454DE2F2}.Debug|x86.Build.0 = Debug|x86 36 | {245A0392-2D46-42DE-A2E6-42A5454DE2F2}.Release|x86.ActiveCfg = Release|x86 37 | {245A0392-2D46-42DE-A2E6-42A5454DE2F2}.Release|x86.Build.0 = Release|x86 38 | {93A24B8C-C195-45B7-BF6B-7F01622D83F7}.Debug|x86.ActiveCfg = Debug|x86 39 | {93A24B8C-C195-45B7-BF6B-7F01622D83F7}.Debug|x86.Build.0 = Debug|x86 40 | {93A24B8C-C195-45B7-BF6B-7F01622D83F7}.Release|x86.ActiveCfg = Release|x86 41 | {93A24B8C-C195-45B7-BF6B-7F01622D83F7}.Release|x86.Build.0 = Release|x86 42 | {AA20B0B4-0773-49C5-AD11-D3C156E4B865}.Debug|x86.ActiveCfg = Debug|x86 43 | {AA20B0B4-0773-49C5-AD11-D3C156E4B865}.Debug|x86.Build.0 = Debug|x86 44 | {AA20B0B4-0773-49C5-AD11-D3C156E4B865}.Release|x86.ActiveCfg = Release|x86 45 | {AA20B0B4-0773-49C5-AD11-D3C156E4B865}.Release|x86.Build.0 = Release|x86 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | EndGlobal 51 | --------------------------------------------------------------------------------