aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiguel Costa <miguel.costa@qt.io>2019-03-18 18:41:39 +0100
committerMiguel Costa <miguel.costa@qt.io>2019-05-02 09:29:01 +0000
commitce7d6f0228b5714cf7302506df13fce62fa0efae (patch)
treede64f2164e0c2a47468b0702bd04f80dea7bc6b9
parent468e1a340243712eb045fabe796532ca10682b5b (diff)
Add Regex wrapper
Adding a new module to the Qt VS Tools solution, a class library that provides a wrapper for .NET Regex objects. This module allows defining regular expressions using C# statements instead of strings. These statements evaluate to instances of the RegExpr class, which can then be translated to the syntax used by .NET's Regex. The pattern statements of RegExpr also allow the definition of tokens. These are mapped to capture groups in Regex and may contain production rules to transform captured text into instances of externally defined types (e.g. an abstract syntax tree). This module is intended to replace the use of .NET Regex in the Qt VS Tools projects, with a view to making code more readable and maintainable. For further details, see included README file. Change-Id: I6509eba7cac0e20a2abeea4f3d2960ff0cb02ab8 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--src/QtVsTools.sln29
-rw-r--r--src/qtvstools.regexpr/Properties/AssemblyInfo.cs36
-rw-r--r--src/qtvstools.regexpr/QtVsTools.RegExpr.csproj97
-rw-r--r--src/qtvstools.regexpr/README317
-rw-r--r--src/qtvstools.regexpr/expression/CharClassLiteral.cs88
-rw-r--r--src/qtvstools.regexpr/expression/CharClassRange.cs98
-rw-r--r--src/qtvstools.regexpr/expression/CharClassSet.cs334
-rw-r--r--src/qtvstools.regexpr/expression/RegExpr.cs114
-rw-r--r--src/qtvstools.regexpr/expression/RegExprAssert.cs182
-rw-r--r--src/qtvstools.regexpr/expression/RegExprChoice.cs87
-rw-r--r--src/qtvstools.regexpr/expression/RegExprLiteral.cs72
-rw-r--r--src/qtvstools.regexpr/expression/RegExprRepeat.cs120
-rw-r--r--src/qtvstools.regexpr/expression/RegExprSequence.cs67
-rw-r--r--src/qtvstools.regexpr/expression/RegExprToken.cs367
-rw-r--r--src/qtvstools.regexpr/expression/Renderer.cs123
-rw-r--r--src/qtvstools.regexpr/parser/ParseTree.cs230
-rw-r--r--src/qtvstools.regexpr/parser/Parser.cs152
-rw-r--r--src/qtvstools.regexpr/production/Production.cs312
-rw-r--r--src/qtvstools.regexpr/production/ProductionRule.cs409
-rw-r--r--src/qtvstools.regexpr/production/ProductionRuleAction.cs345
-rw-r--r--src/qtvstools.regexpr/utils/Consts.cs141
-rw-r--r--src/qtvstools.regexpr/utils/Utils.cs97
-rw-r--r--src/tests/Test_QtVsTools.RegExpr/Properties/AssemblyInfo.cs47
-rw-r--r--src/tests/Test_QtVsTools.RegExpr/Test_MacroParser.cs66
-rw-r--r--src/tests/Test_QtVsTools.RegExpr/Test_QtVsTools.RegExpr.csproj108
-rw-r--r--src/tests/Test_QtVsTools.RegExpr/Test_XmlIntParser.cs344
-rw-r--r--src/tests/Test_QtVsTools.RegExpr/packages.config5
27 files changed, 4387 insertions, 0 deletions
diff --git a/src/QtVsTools.sln b/src/QtVsTools.sln
index 32ebe5e8..ed06c248 100644
--- a/src/QtVsTools.sln
+++ b/src/QtVsTools.sln
@@ -91,6 +91,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QtVSTools Version", "QtVSTo
version.targets = version.targets
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QtVsTools.RegExpr", "QtVsTools.RegExpr\QtVsTools.RegExpr.csproj", "{0BDF77D1-4705-402C-8E58-F0D4D2679C08}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test_QtVsTools.RegExpr", "tests\Test_QtVsTools.RegExpr\Test_QtVsTools.RegExpr.csproj", "{D574EFED-5E19-45BE-9B05-310F65065303}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -227,6 +231,30 @@ Global
{B12702AD-ABFB-343A-A199-8E24837244A3}.Release|Any CPU.Build.0 = Release|Win32
{B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x86.ActiveCfg = Release|Win32
{B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x86.Build.0 = Release|Win32
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Debug|x64.Build.0 = Debug|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Debug|x86.Build.0 = Debug|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Release|x64.ActiveCfg = Release|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Release|x64.Build.0 = Release|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Release|x86.ActiveCfg = Release|Any CPU
+ {0BDF77D1-4705-402C-8E58-F0D4D2679C08}.Release|x86.Build.0 = Release|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Debug|x64.Build.0 = Debug|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Debug|x86.Build.0 = Debug|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Release|x64.ActiveCfg = Release|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Release|x64.Build.0 = Release|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Release|x86.ActiveCfg = Release|Any CPU
+ {D574EFED-5E19-45BE-9B05-310F65065303}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -243,6 +271,7 @@ Global
{F7407750-5F72-460F-9C53-27CF509A39B1} = {D73514C8-019E-44FB-8D42-F1FD52C1FD72}
{F2166B59-E41B-4328-B31D-9E2B9AC5A59C} = {D73514C8-019E-44FB-8D42-F1FD52C1FD72}
{62102FDD-016A-4FEC-B73C-808C7B1C7AA5} = {AE9DC593-DC68-45D2-9A96-E80A7CF3BA7F}
+ {D574EFED-5E19-45BE-9B05-310F65065303} = {3956AF5F-164C-4D38-B5B3-298D9250C193}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EDED4DBD-13ED-475C-B6CE-30AB88EDA03D}
diff --git a/src/qtvstools.regexpr/Properties/AssemblyInfo.cs b/src/qtvstools.regexpr/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..8d3c0ea4
--- /dev/null
+++ b/src/qtvstools.regexpr/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("QtVsTools.RegExpr")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("QtVsTools.RegExpr")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("0bdf77d1-4705-402c-8e58-f0d4d2679c08")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/qtvstools.regexpr/QtVsTools.RegExpr.csproj b/src/qtvstools.regexpr/QtVsTools.RegExpr.csproj
new file mode 100644
index 00000000..16ef9403
--- /dev/null
+++ b/src/qtvstools.regexpr/QtVsTools.RegExpr.csproj
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="$(VisualStudioVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--
+ *****************************************************************************
+ **
+ ** Copyright (C) 2019 The Qt Company Ltd.
+ ** Contact: https://www.qt.io/licensing/
+ **
+ ** This file is part of the Qt VS Tools.
+ **
+ ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+ ** Commercial License Usage
+ ** Licensees holding valid commercial Qt licenses may use this file in
+ ** accordance with the commercial license agreement provided with the
+ ** Software or, alternatively, in accordance with the terms contained in
+ ** a written agreement between you and The Qt Company. For licensing terms
+ ** and conditions see https://www.qt.io/terms-conditions. For further
+ ** information use the contact form at https://www.qt.io/contact-us.
+ **
+ ** GNU General Public License Usage
+ ** Alternatively, this file may be used under the terms of the GNU
+ ** General Public License version 3 as published by the Free Software
+ ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+ ** included in the packaging of this file. Please review the following
+ ** information to ensure the GNU General Public License requirements will
+ ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+ **
+ ** $QT_END_LICENSE$
+ **
+ *****************************************************************************
+-->
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{0BDF77D1-4705-402C-8E58-F0D4D2679C08}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>QtVsTools.RegExpr</RootNamespace>
+ <AssemblyName>QtVsTools.RegExpr</AssemblyName>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Expression\CharClassLiteral.cs" />
+ <Compile Include="Expression\CharClassRange.cs" />
+ <Compile Include="Expression\CharClassSet.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Expression\RegExpr.cs" />
+ <Compile Include="Parser\ParseTree.cs" />
+ <Compile Include="Production\ProductionRuleAction.cs" />
+ <Compile Include="Expression\RegExprAssert.cs" />
+ <Compile Include="Expression\RegExprChoice.cs" />
+ <Compile Include="Utils\Consts.cs" />
+ <Compile Include="Expression\RegExprLiteral.cs" />
+ <Compile Include="Parser\Parser.cs" />
+ <Compile Include="Production\Production.cs" />
+ <Compile Include="Production\ProductionRule.cs" />
+ <Compile Include="Expression\Renderer.cs" />
+ <Compile Include="Expression\RegExprRepeat.cs" />
+ <Compile Include="Expression\RegExprSequence.cs" />
+ <Compile Include="Expression\RegExprToken.cs" />
+ <Compile Include="Utils\Utils.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="README" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/src/qtvstools.regexpr/README b/src/qtvstools.regexpr/README
new file mode 100644
index 00000000..ac5949f4
--- /dev/null
+++ b/src/qtvstools.regexpr/README
@@ -0,0 +1,317 @@
+
+==== RegExpr: (not just) a C# wrapper for System.Text.RegularExpressions.Regex
+
+The purpose of this module is to allow:
+
+ 1. Defining regular expressions as C# code instead of plain-old-strings;
+
+ 2. Marking elements of regular expressions as tokens, allowing captured text to be accessed
+ and manipulated through token IDs;
+
+ 3. Creating token production rules that specify how to process the captured tokens.
+
+
+== 0. "TL;DR"
+
+ * Regular expressions can be written as C# statements without any additional pre-processing.
+
+ * A token definition within a regular expression allows matched text to be captured.
+
+ * Tokens can include production rules that calculate an output object when matching the token.
+
+ * Only one rule from the list of available rules in a token will be selected during parsing.
+
+ * A rule can define a list of actions to be executed in sequence when that rule is selected.
+
+ * Parser output will include all objects created by production rule actions.
+
+
+== 1. Regular expressions as C# statements
+
+The classes in this module can be instantiated using C# statements to specify regular expressions
+that are checked at compile-time, unlike plain-old-strings. Specifying reg-ex'es directly in C#
+will potentially also make them more readable and maintainable.
+
+The following class hierarchy provides abstract representations of regular expressions:
+
+ abstract RegExpr . . . . . . . . Base class of the regular expression abstraction
+ ^
+ |
+ +--+ abstract CharClass . . . . Match one character of a class of characters
+ | ^
+ | |
+ | +--+ CharClassLiteral . . . Match one character of a list of characters
+ | |
+ | +--+ CharClassRange . . . . Match one character of a range of characters
+ | |
+ | +--+ CharClassSet . . . . . Match one character of a set of character classes
+ |
+ +--+ RegExprLiteral . . . . . . Match a sequence of characters
+ |
+ +--+ RegExprRepeat . . . . . . . Match the same pattern repeatedly
+ |
+ +--+ RegExprSequence . . . . . . Match several patterns in sequence
+ |
+ +--+ RegExprChoice . . . . . . . Match one of several alternative patterns
+ |
+ +--+ RegExprAssert . . . . . . . Assert a pattern but do not consume any characters
+ |
+ +--+ Token . . . . . . . . . . . Capture and process text matched by a pattern
+
+The following syntax can be used to specify regular expressions in C# using RegExpr classes
+(the notation _T_ represents an instance of type T):
+
+ Expression Type Description
+----------------------------------------------------------------------------------------------------
+ CharWord CharClassLiteral Word character (\w)
+
+ CharCr CharClassLiteral Carriage return character (\r)
+
+ CharLf CharClassLiteral Line feed character (\n)
+
+ CharSpace CharClassLiteral Space character (\s)
+
+ CharNonSpace CharClassLiteral Non-space character (\S = [^\s])
+
+ CharVertSpace CharClassSet Vertical space ([\r\n])
+
+ CharHorizSpace CharClassSet Horizontal space ([^\S\r\n])
+
+ Char [ _char_ ] CharClassLiteral Literal character class
+
+ Char [ _string_ ] CharClassLiteral Literal character class
+
+ Char [ _char_ , _char_ ] CharClassRange Range character class
+
+ ~ _CharClass_ CharClassSet Negated character class
+
+ CharSet [ _CharClass_ + _CharClass_ ] CharClassSet Combined character class
+
+ CharSet [ _CharClass_ - _CharClass_ ] CharClassSet Character class subtraction
+
+ CharSetRaw [ _string_ ] CharClassLiteral Raw (unescaped) character class
+
+ AnyChar RegExprLiteral Match any character (.)
+
+ StartOfLine RegExprLiteral Anchor for start of line (^)
+
+ EndOfFile RegExprLiteral Anchor for end of input string ($)
+
+ LineBreak RegExprSequence Match a line break (\r?\n)
+
+ RegX ( _string_ ) RegExprLiteral Literal character sequence
+
+ _RegExpr_ .Optional() RegExprRepeat Optional match
+
+ _RegExpr_ .Repeat() RegExprRepeat Match zero or more times
+
+ _RegExpr_ .Repeat ( atLeast: _int_ ) RegExprRepeat Match at least N times
+
+ _RegExpr_ .Repeat ( atMost: _int_ ) RegExprRepeat Match at most N times
+
+ _RegExpr_ .Repeat ( _int_ ) RegExprRepeat Match exactly N times
+
+ _RegExpr_ .Repeat ( _int_ , _int_ ) RegExprRepeat Match between N and M times
+
+ _RegExpr_ & _RegExpr_ RegExprSequence Sequential composition
+
+ _RegExpr_ | _RegExpr_ RegExprChoice Alternating composition
+
+ Assert [ _RegExpr_ ] RegExprAssert Positive assertion (look ahead)
+
+ !Assert [ _RegExpr_ ] RegExprAssert Negative assertion (look ahead)
+
+ Assert [ _RegExpr_ ] > _RegExpr_ RegExprSequence Assert look ahead before match
+
+ _RegExpr_ > Assert [ _RegExpr_ ] RegExprSequence Assert look ahead after match
+
+ Assert [ _RegExpr_ ] < _RegExpr_ RegExprSequence Assert look behind before match
+
+ _RegExpr_ < Assert [ _RegExpr_ ] RegExprSequence Assert look behind after match
+
+ !Assert [ _RegExpr_ ] > _RegExpr_ RegExprSequence Negative look ahead before match
+
+ _RegExpr_ > !Assert [ _RegExpr_ ] RegExprSequence Negative look ahead after match
+
+ !Assert [ _RegExpr_ ] < _RegExpr_ RegExprSequence Negative look behind before match
+
+ _RegExpr_ < !Assert [ _RegExpr_ ] RegExprSequence Negative look behind after match
+
+ RegXRaw ( _string_ ) RegExprLiteral Raw (unescaped) Regex string
+
+Examples of regular expressions as C# statements:
+
+ RegExpr (C#) Regular Expression
+----------------------------------------------------------------------
+ Char["abc"] [abc]
+
+ Char['a', 'z'] [a-z]
+
+ ~Char["abc"] [^abc]
+
+ CharSet[Char["abc"] + Char['x', 'z']] [abcx-z]
+
+ CharSet[Char['a', 'z'] - Char["aeiou"]] [a-z-[aeiou]]
+
+ CharSetRaw["az-[aeiou]"] [a-z-[aeiou]]
+
+ RegX("abc") abc
+
+ RegX(@"\a\b\c") \\a\\b\\c
+
+ RegXRaw(@"\S\r\n") \S\r\n
+
+ RegX("a").Optional() a?
+
+ RegX("a").Repeat() a*
+
+ RegX("a").Repeat(atLeast: 1) a+
+
+ RegX("a").Repeat(atLeast: 2) a{2,}
+
+ RegX("a").Repeat(atMost: 3) a{,3}
+
+ RegX("a").Repeat(4) a{4}
+
+ RegX("a").Repeat(5, 6) a{5,6}
+
+ RegX("a") & "xyz" axyz
+
+ RegX("a") | "xyz" (?:a)|(?:xyz)
+
+ Assert["abc"] (?=abc)
+
+ Assert[RegX("abc")] > CharWord.Repeat() (?=abc)\w*
+
+ !Assert[RegX("abc") > CharWord.Repeat() (?!abc)\w*
+
+ RegX("abc") > Assert[RegX("xyz")] abc(?=xyz)
+
+ Assert[RegX("abc")] < RegX("xyz") (?<=abc)xyz
+
+ CharWord.Repeat() < Assert[RegX("xyz")] \w*(?<=xyz)
+
+ CharWord.Repeat() < !Assert[RegX("xyz")] \w*(?<!xyz)
+
+
+== 2. Tokens
+
+The following statement creates a token based on a RegExpr:
+
+ new Token ( _string_ , _RegExpr_ )
+
+The string param will be used as an ID to reference the text captured by the RegExpr.
+The whitespace immediately before a token can be automatically skipped. A RegExpr to match the
+whitespace can be provided as a default for all tokens, or given specifically to each token:
+
+ new Token ( _string_ , _RegExpr_ , _RegExpr_ )
+
+The first RegExpr param specifies the pattern of leading whitespace to be skipped for this token.
+Whitespace skipping can be disabled for specific tokens:
+
+ new Token ( _string_ , SkipWs_Disable , _RegExpr_ )
+
+The following are examples of token definitions:
+
+ new Token("NUM", Char['0', '9'].Repeat())
+
+ new Token("WORD", Space.Repeat(), CharWord.Repeat())
+
+ new Token("STRING", SkipWs_Disable, ~Char['\"'].Repeat())
+
+
+== 3. Production rules
+
+By default, a token will output the string that was captured by the specified RegExpr. It is
+possible to define production rules for a token, indicating how to instantiate an arbitrary output
+object based on content captured by that token or other tokens. This uses the following syntax:
+(the notation (_T1_, _T2_, ...) => _T_ represents a callback delegate with param types T1, T2, etc.,
+and return type T; in case of void callback, return type is given as _void_ )
+
+ new Token ( _string_ , _RegExpr_)
+ {
+ new Rule < T > (
+ priority: _int_ ,
+ select: (_Token_) => _bool_ ,
+ pre: (_Token_) => _bool_ )
+ {
+ _RuleAction_,
+ ...
+ },
+ ...
+ }
+
+'T' stands for the output type when the token is matched. In this case, use of the Rule<T> class
+means that the token will produce an object of type T based only on the captured string. More
+complex rules can be specified, which will allow the analysis of syntaxes with recursively delimited
+expressions (e.g. expressions with nested parentheses), and infix, prefix or postfix operators:
+
+ PrefixRule < T , TOperand > . . . . . . . . . . . . Prefix operator
+
+ PostfixRule < TOperand , T > . . . . . . . . . . . Postfix operator
+
+ InfixRule < TLeftOperand , T , TRightOperand > . . Infix operator
+
+ LeftDelimiterRule < T > . . . . . . . . . . . . . . Left delimiter (e.g. open parenthesis)
+
+ RightDelimiterRule< TLeftDelim , TExpr , T > . . . Left delimiter (e.g. close parenthesis)
+
+When capturing text, only one of the rules in the token definition will be applied. The conditions
+for token rule selection are specified by rule selector predicates given in the "select:" param.
+If a selector predicate fails, the parser will try to select another rule for the token.
+
+A rule pre-condition can also be specified. This will be tested after the rule was selected and
+just before it is executed. If the pre-condition fails, this will generate a parse error.
+
+To obtain the actual production, i.e. instantiation of an output object, a production rule needs
+to provide one or more actions that describe how to create or manipulate the production object.
+There are 4 types of possible actions that can be specified inside a production rule:
+
+ Capture < T > ( _(string)_ => _T_ ) New production from token capture
+
+ Create < T , ... > ( _(...)_ => _T_ ) New production from operand productions
+
+ Transform < T , ... > ( _(T,...)_ => _T_ ) New production from current value and operands
+
+ Update < T , ... > ( _(T,...)_ => _void_ ) Update current production value
+
+Actions may also specify a condition predicate for its params; if the predicate fails, the action
+will not be taken and rule execution will continue with the next action in the list.
+
+A non producing action 'Error' can also be specified within a rule to provide additional syntax
+verification. When an Error predicate is verified, this action will produce a string corresponding
+to a parse error message; parsing stops at this point.
+
+
+== 4. Examples
+
+The following token will match a decimal constant and output its int value:
+
+ new Token("NUM", Char['0', '9'].Repeat())
+ {
+ new Rule<int>
+ {
+ Capture(value => int.Parse(value))
+ }
+ }
+
+The following token will match the operator '+' in the context of an int expression:
+
+ new Token("PLUS", "+")
+ {
+ new PrefixRule<int, int>(
+ priority: PRIORITY_PREFIX,
+ select: t => (t.IsFirst || t.LookBehind().First().Is("LEFT_PAR"))
+ && t.LookAhead().First().Is("NUM", "LEFT_PAR"))
+ {
+ Create((int x) => +x)
+ },
+
+ new InfixRule<int, int, int>(priority: PRIORITY_INFIX)
+ {
+ Create((int x, int y) => x + y)
+ }
+ };
+
+More detailed examples of the use of the RegExpr module can be found in the provided auto-tests
+(project Test_QtVsTools.RegExpr).
diff --git a/src/qtvstools.regexpr/expression/CharClassLiteral.cs b/src/qtvstools.regexpr/expression/CharClassLiteral.cs
new file mode 100644
index 00000000..c37fbdeb
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/CharClassLiteral.cs
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// CharClassLiteral ( -> CharClassSet.Element -> CharClass -> RegExpr )
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Elementary character class defined by one or more allowed characters
+ /// </summary>
+ ///
+ public class CharClassLiteral : CharClassSet.Element
+ {
+ public string LiteralChars { get; set; }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+ if ((parent == null || !(parent is CharClass)) && NeedsGroup(LiteralChars))
+ pattern.AppendFormat("[{0}]", LiteralChars);
+ else
+ pattern.Append(LiteralChars);
+ return null;
+ }
+ }
+
+ public abstract partial class CharClass : RegExpr
+ {
+ public static CharClassLiteral CharLiteral(string s)
+ {
+ return new CharClassLiteral
+ {
+ LiteralChars = Escape(s)
+ };
+ }
+
+ public static CharClassLiteral CharRawLiteral(string s)
+ {
+ return new CharClassLiteral
+ {
+ LiteralChars = s
+ };
+ }
+
+ public static CharClassLiteral CharLiteral(char c)
+ {
+ return CharLiteral(c.ToString());
+ }
+
+ public partial class CharExprBuilder
+ {
+ public CharClassLiteral this[string s] { get { return CharLiteral(s); } }
+ public CharClassLiteral this[char c] { get { return CharLiteral(c); } }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/CharClassRange.cs b/src/qtvstools.regexpr/expression/CharClassRange.cs
new file mode 100644
index 00000000..235a3463
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/CharClassRange.cs
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// CharClassRange ( -> CharClassSet.Element -> CharClass -> RegExpr )
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Represents an elementary character class defined by a range of characters
+ /// </summary>
+ ///
+ public partial class CharClassRange : CharClassSet.Element
+ {
+ public CharClassLiteral LowerBound { get; set; }
+ public CharClassLiteral UpperBound { get; set; }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+
+ if (parent == null || !(parent is CharClass))
+ pattern.Append("[");
+
+ return Items(LowerBound, UpperBound);
+ }
+
+ protected override void OnRenderNext(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderNext(defaultTokenWs, parent, pattern, ref mode);
+ pattern.Append("-");
+ }
+
+ protected override void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderEnd(defaultTokenWs, parent, pattern, ref mode);
+
+ if (parent == null || !(parent is CharClass))
+ pattern.Append("]");
+ }
+ }
+
+ public abstract partial class CharClass : RegExpr
+ {
+ public static CharClassRange CharRange(string lBound, string uBound)
+ {
+ return CharRange(CharLiteral(lBound), CharLiteral(uBound));
+ }
+
+ public static CharClassRange CharRange(CharClassLiteral lBound, CharClassLiteral uBound)
+ {
+ return new CharClassRange
+ {
+ LowerBound = lBound,
+ UpperBound = uBound
+ };
+ }
+
+ public partial class CharExprBuilder
+ {
+ public CharClassRange this[char lBound, char uBound]
+ { get { return CharRange(lBound.ToString(), uBound.ToString()); } }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/CharClassSet.cs b/src/qtvstools.regexpr/expression/CharClassSet.cs
new file mode 100644
index 00000000..d8ce0057
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/CharClassSet.cs
@@ -0,0 +1,334 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ using static CharClassSet;
+ using static CharClass.CharSetExprBuilder;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// CharClassSet ( -> CharClass -> RegExpr )
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Represents a complex class defined by a combination of elementary character classes.
+ /// </summary>
+ ///
+ public partial class CharClassSet : CharClass, IEnumerable<IEnumerable<Element>>
+ {
+ public IEnumerable<Element> Positives { get; set; }
+ public IEnumerable<Element> Negatives { get; set; }
+
+ bool IsSubSet { get; set; }
+ bool HasPositive { get { return Positives != null && Positives.Any(); } }
+ bool HasNegative { get { return Negatives != null && Negatives.Any(); } }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+
+ if (!HasPositive && !HasNegative)
+ return null;
+
+ if (!IsSubSet) {
+ if (HasPositive)
+ pattern.Append("[");
+ else
+ pattern.Append("[^");
+ }
+
+ IEnumerable<RegExpr> children = null;
+ if (HasPositive && HasNegative) {
+ children = Items(
+ new CharClassSet(positives: Positives) { IsSubSet = true },
+ new CharClassSet(negatives: Negatives) { IsSubSet = true });
+ } else {
+ if (HasPositive)
+ children = Positives;
+ else if (HasNegative)
+ children = Negatives;
+ }
+
+ return children;
+ }
+
+ protected override void OnRenderNext(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderNext(defaultTokenWs, parent, pattern, ref mode);
+ if (!IsSubSet && HasPositive && HasNegative)
+ pattern.Append("-[");
+ }
+
+ protected override void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderEnd(defaultTokenWs, parent, pattern, ref mode);
+ if (!IsSubSet) {
+ if (HasPositive && HasNegative)
+ pattern.Append("]]");
+ else
+ pattern.Append("]");
+ }
+ }
+
+ public const bool Invert = true;
+ public const bool Positive = true;
+
+ public CharClassSet(
+ IEnumerable<Element> positives = null,
+ IEnumerable<Element> negatives = null)
+ {
+ Positives = positives != null ? positives : Empty<Element>();
+ Negatives = negatives != null ? negatives : Empty<Element>();
+ }
+
+ public CharClassSet(Element element, bool negative = false) : this()
+ {
+ if (negative)
+ Negatives = Items(element);
+ else
+ Positives = Items(element);
+ }
+
+ public CharClassSet(CharClassSet set, bool invert = false) : this()
+ {
+ Add(set, invert);
+ }
+
+ public void Add(Element element, bool negative = false)
+ {
+ if (negative)
+ Negatives = Negatives.Concat(Items(element));
+ else
+ Positives = Positives.Concat(Items(element));
+ }
+
+ public void Add(CharClassSet set, bool invert = false)
+ {
+ Positives = Positives.Concat(!invert ? set.Positives : set.Negatives);
+ Negatives = Negatives.Concat(!invert ? set.Negatives : set.Positives);
+ }
+
+ public IEnumerator<IEnumerable<Element>> GetEnumerator()
+ {
+ return (new[] { Positives, Negatives }).AsEnumerable().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public abstract class Element : CharClass
+ {
+ public static CharClassSet operator ~(Element x)
+ { return new CharClassSet(x, negative: true); }
+
+ public static PositiveSet operator +(Element x, Element y)
+ { return new PositiveSet(Op.Plus, x, y); }
+
+ public static PositiveSet operator +(Element x, PositiveSet y)
+ { return new PositiveSet(Op.Plus, x, y); }
+
+ public static PositiveSet operator +(PositiveSet x, Element y)
+ { return new PositiveSet(Op.Plus, x, y); }
+
+ public static Expr operator -(Element x, Element y)
+ { return new Expr(Op.Minus, x, y); }
+
+ public static Expr operator -(Element x, PositiveSet y)
+ { return new Expr(Op.Minus, x, y); }
+
+ public static Expr operator -(PositiveSet x, Element y)
+ { return new Expr(Op.Minus, x, y); }
+ }
+
+ public static CharClassSet operator ~(CharClassSet x)
+ { return new CharClassSet(x, invert: true); }
+
+ public static Expr operator +(Element x, CharClassSet y)
+ { return new Expr(Op.Plus, x, y); }
+
+ public static Expr operator +(CharClassSet x, Element y)
+ { return new Expr(Op.Plus, x, y); }
+
+ public static Expr operator +(PositiveSet x, CharClassSet y)
+ { return new Expr(Op.Plus, x, y); }
+
+ public static Expr operator +(CharClassSet x, PositiveSet y)
+ { return new Expr(Op.Plus, x, y); }
+
+ public static Expr operator +(CharClassSet x, CharClassSet y)
+ { return new Expr(Op.Plus, x, y); }
+
+ public static Expr operator -(CharClassSet x, Element y)
+ { return new Expr(Op.Minus, x, y); }
+
+ public static Expr operator -(CharClassSet x, PositiveSet y)
+ { return new Expr(Op.Minus, x, y); }
+ }
+
+ public abstract partial class CharClass : RegExpr
+ {
+ public partial class CharSetExprBuilder
+ {
+ public enum Op { Term, Tilde, Plus, Minus }
+
+ public class Expr
+ {
+ public Op Operator { get; private set; }
+
+ public List<Expr> Factors { get; private set; }
+ public Expr(Op op, List<Expr> factors) { Operator = op; Factors = factors; }
+ public Expr(Op op, params Expr[] factors) : this(op, factors.ToList()) { }
+
+ public CharClass Term { get; private set; }
+ public Expr(CharClass c) { Operator = Op.Term; Term = c; }
+ public static implicit operator Expr(CharClass c) { return new Expr(c); }
+ public static Expr operator ~(Expr x)
+ { return new Expr(Op.Tilde, x); }
+ }
+
+ public class PositiveSet : Expr
+ {
+ public PositiveSet(Op op, params Expr[] factors) : base(op, factors) { }
+
+ public static PositiveSet operator +(PositiveSet x, PositiveSet y)
+ { return new PositiveSet(Op.Plus, x, y); }
+
+ public static Expr operator -(PositiveSet x, PositiveSet y)
+ { return new Expr(Op.Minus, x, y); }
+ }
+
+ public CharClassSet this[Expr expr]
+ {
+ get
+ {
+ var stack = new Stack<StackFrame>();
+ stack.Push(expr);
+ CharClassSet classSet = null;
+
+ while (stack.Any()) {
+ var context = stack.Pop();
+ expr = context.Expr;
+ if (context == null || expr == null) {
+ continue;
+ } else if (context.Children == null) {
+ context.Children = new Queue<Expr>();
+ if (expr.Factors != null && expr.Factors.Any())
+ expr.Factors.ForEach(x => context.Children.Enqueue(x));
+ stack.Push(context);
+ continue;
+ } else if (context.Children.Any()) {
+ expr = context.Children.Dequeue();
+ stack.Push(context);
+ stack.Push(expr);
+ continue;
+ }
+
+ classSet = null;
+ if (expr.Operator == Op.Term) {
+ if (expr.Term is CharClassSet)
+ classSet = expr.Term as CharClassSet;
+ else
+ classSet = new CharClassSet(expr.Term as Element);
+ } else if (context.SubSets != null && context.SubSets.Any()) {
+ switch (expr.Operator) {
+ case Op.Tilde:
+ classSet = new CharClassSet
+ {
+ { context.SubSets.First(), Invert }
+ };
+ break;
+ case Op.Plus:
+ classSet = new CharClassSet
+ {
+ { context.SubSets.First() },
+ { context.SubSets.Last() }
+ };
+ break;
+ case Op.Minus:
+ classSet = new CharClassSet
+ {
+ { context.SubSets.First() },
+ { context.SubSets.Last(), Invert }
+ };
+ break;
+ }
+ }
+
+ var parentContext = stack.Any() ? stack.Peek() : null;
+ if (classSet != null && parentContext != null)
+ parentContext.SubSets.Add(classSet);
+ }
+
+ if (classSet == null)
+ throw new CharClassEvalException();
+
+ return classSet;
+ }
+ }
+
+ class StackFrame
+ {
+ public Expr Expr { get; set; }
+ public Queue<Expr> Children { get; set; }
+ public List<CharClassSet> SubSets { get; set; }
+ public StackFrame()
+ {
+ Expr = null;
+ Children = null;
+ SubSets = new List<CharClassSet>();
+ }
+ public static implicit operator StackFrame(Expr e)
+ {
+ return new StackFrame { Expr = e };
+ }
+ }
+ }
+
+ public partial class CharSetRawExprBuilder
+ {
+ public CharClassLiteral this[string s] { get { return CharRawLiteral(s); } }
+ }
+
+ public class CharClassEvalException : RegExpr.Exception
+ {
+ public CharClassEvalException(string message = null) : base(message) { }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExpr.cs b/src/qtvstools.regexpr/expression/RegExpr.cs
new file mode 100644
index 00000000..999eb8fa
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExpr.cs
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExpr
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Abstract representation of a regular expression.
+ /// </summary>
+ /// <remarks>
+ /// RegExpr objects can be created and combined using C# expressions, and then rendered into a
+ /// regular expression pattern and parser.
+ /// </remarks>
+ public abstract partial class RegExpr
+ {
+ /// <summary>
+ /// Render the RegExpr into a corresponding pattern and parser.
+ /// </summary>
+ /// <param name="defaultTokenWs">Default token whitespace</param>
+ /// <returns>
+ /// <see cref="Parser"/> object that can process strings according to the pattern rendered.
+ /// </returns>
+ public Parser Render(RegExpr defaultTokenWs = null)
+ {
+ return new Parser(this, defaultTokenWs);
+ }
+
+ [Flags]
+ protected enum RenderMode { Default = 0, Assert = 1 }
+
+ /// <summary>
+ /// Event triggered when starting the rendering process for this RegExpr.
+ /// </summary>
+ /// <param name="defaultTokenWs">Default token whitespace</param>
+ /// <param name="parent">Parent expression</param>
+ /// <param name="pattern">Rendered pattern</param>
+ /// <param name="mode">Rendering mode</param>
+ ///
+ /// <returns>Sub-expressions to add to the rendering process.</returns>
+ /// <remarks>
+ /// RegExpr sub-classes will re-implement OnRenderBegin, OnRenderNext and OnRenderEnd to
+ /// define their specific rendering process. Regular expression strings are appended to
+ /// <paramref name="pattern"/>. When rendering a named capture group, a mapping to a
+ /// production object can be defined and added to <paramref name="prodMap"/>. The production
+ /// object will be responsible for translating the captured values into instances of
+ /// external, application specific classes.
+ /// </remarks>
+ protected virtual IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ { return null; }
+
+ /// <summary>
+ /// Event triggered when rendering the next sub-expression.
+ /// </summary>
+ /// <param name="defaultTokenWs">Default token whitespace</param>
+ /// <param name="parent">Parent expression</param>
+ /// <param name="pattern">Rendered pattern</param>
+ /// <param name="mode">Rendering mode</param>
+ ///
+ protected virtual void OnRenderNext(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ { }
+
+ /// <summary>
+ /// Event triggered after all sub-expressions have been rendered.
+ /// </summary>
+ /// <param name="defaultTokenWs">Default token whitespace</param>
+ /// <param name="parent">Parent expression</param>
+ /// <param name="pattern">Rendered pattern</param>
+ /// <param name="mode">Rendering mode</param>
+ ///
+ protected virtual void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ { }
+
+ public class Exception : System.Exception
+ {
+ public Exception(string message = null) : base(message) { }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExprAssert.cs b/src/qtvstools.regexpr/expression/RegExprAssert.cs
new file mode 100644
index 00000000..028e51e1
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExprAssert.cs
@@ -0,0 +1,182 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ using static RegExprAssert;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExprAssert ( -> RegExpr)
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Asserts a pattern on the input string without consuming chars.
+ /// </summary>
+ ///
+ public partial class RegExprAssert : RegExpr
+ {
+ public enum AssertLook { Ahead, Behind }
+
+ public AssertLook Context { get; set; }
+ public bool Negative { get; set; }
+ public RegExpr Expr { get; set; }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+
+ if (mode.HasFlag(RenderMode.Assert))
+ throw new NestedAssertException();
+
+ switch (Context) {
+ case AssertLook.Ahead:
+ if (Negative)
+ pattern.Append("(?!");
+ else
+ pattern.Append("(?=");
+ break;
+ case AssertLook.Behind:
+ if (Negative)
+ pattern.Append("(?<!");
+ else
+ pattern.Append("(?<=");
+ break;
+ }
+
+ mode |= RenderMode.Assert;
+ return Items(Expr);
+ }
+
+ protected override void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderEnd(defaultTokenWs, parent, pattern, ref mode);
+ pattern.Append(")");
+ mode &= ~RenderMode.Assert;
+ }
+ }
+
+ public abstract partial class RegExpr
+ {
+ RegExprAssert AsAssert()
+ {
+ if (this is RegExprAssert)
+ return this as RegExprAssert;
+
+ return new RegExprAssert
+ {
+ Context = AssertLook.Ahead,
+ Negative = false,
+ Expr = this
+ };
+ }
+
+ public static RegExprAssert AssertLookAhead(RegExpr expr)
+ {
+ var assert = expr.AsAssert();
+ return new RegExprAssert
+ {
+ Context = AssertLook.Ahead,
+ Negative = assert.Negative,
+ Expr = assert.Expr
+ };
+ }
+
+ public static RegExprAssert AssertLookBehind(RegExpr expr)
+ {
+ var assert = expr.AsAssert();
+ return new RegExprAssert
+ {
+ Context = AssertLook.Behind,
+ Negative = assert.Negative,
+ Expr = assert.Expr
+ };
+ }
+
+ public static RegExprAssert AssertNegated(RegExpr expr)
+ {
+ var assert = expr.AsAssert();
+ return new RegExprAssert
+ {
+ Context = assert.Context,
+ Negative = !assert.Negative,
+ Expr = assert.Expr
+ };
+ }
+
+ public delegate RegExprAssert AssertTemplate(RegExpr expr);
+
+ public class AssertExprBuilder
+ {
+ AssertTemplate Template { get; set; }
+
+ public AssertExprBuilder(AssertTemplate template)
+ {
+ Template = template;
+ }
+
+ public class Expr
+ {
+ public RegExprAssert Assert { get; set; }
+ public Expr(RegExprAssert assert) { Assert = assert; }
+
+ public static implicit operator RegExpr(Expr e)
+ {
+ return e.Assert;
+ }
+ }
+
+ public class NegateableExpr : Expr
+ {
+ public NegateableExpr(RegExprAssert assert) : base(assert) { }
+
+ public static Expr operator !(NegateableExpr x)
+ {
+ return new Expr(AssertNegated(x.Assert));
+ }
+ }
+
+ public NegateableExpr this[RegExpr expr]
+ {
+ get { return new NegateableExpr(Template(expr)); }
+ }
+ }
+
+ public class NestedAssertException : RegExpr.Exception
+ {
+ public NestedAssertException(string message = null) : base(message) { }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExprChoice.cs b/src/qtvstools.regexpr/expression/RegExprChoice.cs
new file mode 100644
index 00000000..9222c235
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExprChoice.cs
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ /// <summary>
+ /// Alternating composition
+ /// </summary>
+ public partial class RegExprChoice : RegExpr
+ {
+ public IEnumerable<RegExpr> Exprs { get; set; }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+ if (!(parent is Token))
+ pattern.Append("(?:");
+ pattern.Append("(?:");
+ return Exprs;
+ }
+
+ protected override void OnRenderNext(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderNext(defaultTokenWs, parent, pattern, ref mode);
+ pattern.Append(")|(?:");
+ }
+
+ protected override void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderEnd(defaultTokenWs, parent, pattern, ref mode);
+ pattern.Append(")");
+ if (!(parent is Token))
+ pattern.Append(")");
+ }
+ }
+
+ public abstract partial class RegExpr
+ {
+ public static RegExpr Choice(params RegExpr[] rxs)
+ {
+ return new RegExprChoice
+ {
+ Exprs = rxs.SelectMany(rx => rx is RegExprChoice
+ ? ((RegExprChoice)rx).Exprs
+ : Items(rx))
+ };
+
+ }
+
+ public static RegExpr operator |(RegExpr rx1, RegExpr rx2)
+ {
+ return Choice(rx1, rx2);
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExprLiteral.cs b/src/qtvstools.regexpr/expression/RegExprLiteral.cs
new file mode 100644
index 00000000..78f8509f
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExprLiteral.cs
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ /// <summary>
+ /// Literal character sequence
+ /// </summary>
+ public class RegExprLiteral : RegExpr
+ {
+ public string LiteralExpr { get; set; }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+ pattern.Append(LiteralExpr);
+ return null;
+ }
+ }
+
+ public abstract partial class RegExpr
+ {
+ public static RegExprLiteral RegX(string s)
+ {
+ return new RegExprLiteral() { LiteralExpr = Escape(s) };
+ }
+
+ public static RegExprLiteral RegXRaw(string s)
+ {
+ return new RegExprLiteral() { LiteralExpr = s };
+ }
+
+ public static implicit operator RegExpr(string s)
+ {
+ return RegX(s);
+ }
+
+ public static implicit operator RegExpr(char c)
+ {
+ return RegX(c.ToString());
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExprRepeat.cs b/src/qtvstools.regexpr/expression/RegExprRepeat.cs
new file mode 100644
index 00000000..541638e6
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExprRepeat.cs
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ /// <summary>
+ /// Regular expression quantifier.
+ /// </summary>
+ public class RegExprRepeat : RegExpr
+ {
+ public int AtLeast { get; set; }
+ public int AtMost { get; set; }
+ public RegExpr Expr { get; set; }
+
+ bool ExprNeedsGroup
+ {
+ get
+ {
+ return (Expr is RegExprSequence)
+ || (Expr is RegExprLiteral
+ && NeedsGroup(Expr.As<RegExprLiteral>().LiteralExpr));
+ }
+ }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+
+ if (ExprNeedsGroup)
+ pattern.Append("(?:");
+
+ return Items(Expr);
+ }
+
+ protected override void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderEnd(defaultTokenWs, parent, pattern, ref mode);
+
+ if (ExprNeedsGroup)
+ pattern.Append(")");
+
+ if (AtLeast == 0 && AtMost == 1)
+ pattern.Append("?");
+ else if (AtLeast == 0 && AtMost == int.MaxValue)
+ pattern.Append("*");
+ else if (AtLeast == 1 && AtMost == int.MaxValue)
+ pattern.Append("+");
+ else if (AtLeast == AtMost)
+ pattern.AppendFormat("{{{0}}}", AtLeast);
+ else if (AtMost == int.MaxValue)
+ pattern.AppendFormat("{{{0},}}", AtLeast);
+ else if (AtLeast == 0)
+ pattern.AppendFormat("{{,{0}}}", AtMost);
+ else
+ pattern.AppendFormat("{{{0},{1}}}", AtLeast, AtMost);
+ }
+ }
+
+ public abstract partial class RegExpr
+ {
+ public RegExpr Optional()
+ {
+ return Repeat(0, 1);
+ }
+
+ public RegExpr Repeat(int count)
+ {
+ return Repeat(count, count);
+ }
+
+ public RegExpr Repeat(int atLeast = 0, int atMost = int.MaxValue)
+ {
+ if (this is RegExprRepeat)
+ throw new NestedRepeatException();
+
+ return new RegExprRepeat
+ {
+ AtLeast = atLeast,
+ AtMost = atMost,
+ Expr = this
+ };
+ }
+
+ public class NestedRepeatException : RegExpr.Exception
+ {
+ public NestedRepeatException(string message = null) : base(message) { }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExprSequence.cs b/src/qtvstools.regexpr/expression/RegExprSequence.cs
new file mode 100644
index 00000000..71e1491b
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExprSequence.cs
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ /// <summary>
+ /// Sequential composition
+ /// </summary>
+ public partial class RegExprSequence : RegExpr
+ {
+ public IEnumerable<RegExpr> Exprs { get; set; }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+ return Exprs;
+ }
+ }
+
+ public abstract partial class RegExpr
+ {
+ public static RegExprSequence Concat(params RegExpr[] rxs)
+ {
+ return new RegExprSequence
+ {
+ Exprs = rxs.SelectMany(rx => rx is RegExprSequence
+ ? ((RegExprSequence)rx).Exprs
+ : Items(rx))
+ };
+ }
+
+ public static RegExprSequence operator &(RegExpr rx1, RegExpr rx2)
+ {
+ return Concat(rx1, rx2);
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/RegExprToken.cs b/src/qtvstools.regexpr/expression/RegExprToken.cs
new file mode 100644
index 00000000..91dff1bc
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/RegExprToken.cs
@@ -0,0 +1,367 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ public enum SkipWhitespace { Disable, Enable }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExpr.Token
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// A token of the syntax under analysis.
+ /// </summary>
+ /// <remarks>
+ /// Token objects contain an encapsulated <see cref="RegExpr"/> that defines the pattern
+ /// of the token's syntax, rendered as a named capture group. The name of the capture group
+ /// corresponds to the identifier of the token. A Token may also include production rules
+ /// defining the actions to take when content is matched by the associated capture group.
+ /// </remarks>
+ public partial class Token : RegExpr, IEnumerable<IProductionRule>
+ {
+ public string CaptureId { get; private set; }
+ public string Id { get; set; }
+
+ public bool SkipLeadingWhitespace { get; set; }
+ public RegExpr LeadingWhitespace { get; set; }
+
+ public RegExpr Expr { get; set; }
+
+ public Token(string id, RegExpr skipWs, RegExpr expr)
+ {
+ Id = id;
+ SkipLeadingWhitespace = true;
+ LeadingWhitespace = skipWs;
+ Expr = expr;
+ Rules = new TokenRules();
+ CaptureId = GenerateCaptureId(Id);
+ }
+
+ public Token(string id, SkipWhitespace skipWs, RegExpr expr)
+ {
+ Id = id;
+ SkipLeadingWhitespace = (skipWs == SkipWhitespace.Enable);
+ Expr = expr;
+ Rules = new TokenRules();
+ CaptureId = GenerateCaptureId(Id);
+ }
+
+ public Token(Enum id, RegExpr expr)
+ : this(id.ToString(), SkipWhitespace.Enable, expr)
+ { }
+
+ public Token(string id, RegExpr expr)
+ : this(id, SkipWhitespace.Enable, expr)
+ { }
+
+ public Token(Enum id, SkipWhitespace skipWs, RegExpr expr)
+ : this(id.ToString(), skipWs, expr)
+ { }
+
+ public Token(Enum id, RegExpr skipWs, RegExpr expr)
+ : this(id.ToString(), skipWs, expr)
+ { }
+
+ public Token(RegExpr expr)
+ : this(string.Empty, SkipWhitespace.Enable, expr)
+ { }
+
+ public Token()
+ : this(string.Empty, SkipWhitespace.Enable, null)
+ { }
+
+ protected override IEnumerable<RegExpr> OnRender(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRender(defaultTokenWs, parent, pattern, ref mode);
+
+ var tokenWs = GetTokenWhitespace(defaultTokenWs);
+ if (tokenWs != null)
+ pattern.Append("(?:");
+ if (NeedsWhitespaceGroup(tokenWs, mode))
+ pattern.Append("(?:");
+ return Items(tokenWs, Expr);
+ }
+
+ protected override void OnRenderNext(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderNext(defaultTokenWs, parent, pattern, ref mode);
+ var tokenWs = GetTokenWhitespace(defaultTokenWs);
+ if (NeedsWhitespaceGroup(tokenWs, mode))
+ pattern.Append(")");
+ if (Expr != null) {
+ if (!mode.HasFlag(RenderMode.Assert) && !string.IsNullOrEmpty(Id))
+ pattern.AppendFormat("(?<{0}>", CaptureId);
+ else
+ pattern.Append("(?:");
+
+ }
+ }
+
+ protected override void OnRenderEnd(RegExpr defaultTokenWs, RegExpr parent,
+ StringBuilder pattern, ref RenderMode mode)
+ {
+ base.OnRenderEnd(defaultTokenWs, parent, pattern, ref mode);
+ if (Expr != null)
+ pattern.Append(")");
+ var tokenWs = GetTokenWhitespace(defaultTokenWs);
+ if (tokenWs != null)
+ pattern.Append(")");
+ }
+
+ /// <summary>
+ /// Set of rules that can be applied to the token.
+ /// </summary>
+ TokenRules Rules { get; set; }
+
+ public void Add(IProductionRule rule)
+ {
+ Rules.Add(rule);
+ rule.Token = this;
+ }
+
+ public IProductionRule SelectRule(ITokenCapture tokenCapture)
+ {
+ return Rules.Select(tokenCapture);
+ }
+
+ const string TokenUniqueIdTemplate = "TOKEN_{0}_{1}";
+
+ protected static string GenerateCaptureId(string Id)
+ {
+ return string.Concat(string.Format(TokenUniqueIdTemplate,
+ Path.GetRandomFileName().Replace(".", ""), Id)
+ .Take(32));
+ }
+
+ RegExpr GetTokenWhitespace(RegExpr defaultTokenWs)
+ {
+ if (!SkipLeadingWhitespace)
+ return null;
+ var tokenWs = LeadingWhitespace;
+ if (tokenWs == null)
+ tokenWs = defaultTokenWs;
+ return tokenWs;
+ }
+
+ bool NeedsWhitespaceGroup(RegExpr tokenWs, RenderMode mode)
+ {
+ return tokenWs != null && !mode.HasFlag(RenderMode.Assert)
+ && (tokenWs is RegExprLiteral || tokenWs is RegExprSequence);
+ }
+
+ public IEnumerator<IProductionRule> GetEnumerator()
+ {
+ return ((IEnumerable<IProductionRule>)Rules).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable<IProductionRule>)Rules).GetEnumerator();
+ }
+ }
+
+ public class TokenGroup : IEnumerable<string>
+ {
+ HashSet<string> TokenIds { get; set; }
+
+ public TokenGroup(params string[] tokenIds)
+ {
+ TokenIds = new HashSet<string>(tokenIds);
+ }
+
+ public void Add(string tokenId)
+ {
+ TokenIds.Add(tokenId);
+ }
+
+ public void Add(IEnumerable<string> tokenIds)
+ {
+ TokenIds.UnionWith(tokenIds);
+ }
+
+ public void Add(Token token)
+ {
+ Add(token.Id);
+ }
+
+ public static implicit operator TokenGroup(string tokenId)
+ {
+ return new TokenGroup(tokenId);
+ }
+
+ public IEnumerator<string> GetEnumerator()
+ {
+ return TokenIds.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return TokenIds.GetEnumerator();
+ }
+ }
+
+ public class TokenRules : IEnumerable<IProductionRule>
+ {
+ Dictionary<RuleCallback.Selector, IProductionRule> Rules { get; set; }
+ IProductionRule DefaultRule { get; set; }
+
+ public TokenRules()
+ {
+ Rules = new Dictionary<RuleCallback.Selector, IProductionRule>();
+ }
+
+ public void Add(IProductionRule item)
+ {
+ if (item.Selector == Default)
+ DefaultRule = item;
+ else
+ Rules[item.Selector] = item;
+ }
+
+ bool TestSelector(
+ KeyValuePair<RuleCallback.Selector, IProductionRule> pairSelectorRule,
+ ITokenCapture tokenCapture)
+ {
+ var selector = pairSelectorRule.Key;
+ var rule = pairSelectorRule.Value;
+ if (rule == null)
+ return false;
+ if (selector != null && !selector(tokenCapture))
+ return false;
+ return true;
+ }
+
+ public IProductionRule Select(ITokenCapture tokenCapture)
+ {
+ var selectedRules = Rules
+ .Where(rule => TestSelector(rule, tokenCapture))
+ .Select(rule => rule.Value);
+ return selectedRules.Any() ? selectedRules.First() : DefaultRule;
+ }
+
+ public IEnumerator<IProductionRule> GetEnumerator()
+ {
+ return Rules.Values.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return Rules.Values.GetEnumerator();
+ }
+ }
+
+ public interface ITokenCapture
+ {
+ string TokenId { get; }
+ string Value { get; }
+ bool IsFirst { get; }
+ bool IsLast { get; }
+ IEnumerable<ITokenCapture> LookAhead(params TokenGroup[] tokenIds);
+ IEnumerable<ITokenCapture> LookBehind(params TokenGroup[] tokenIds);
+ bool Is(params TokenGroup[] tokenIds);
+ bool IsNot(params TokenGroup[] tokenIds);
+ }
+
+ public interface IOperatorCapture : ITokenCapture
+ {
+ IOperandCapture Operand { get; }
+ IOperandCapture LeftOperand { get; }
+ IOperandCapture RightOperand { get; }
+ bool HasOperand { get; }
+ bool HasLeftOperand { get; }
+ bool HasRightOperand { get; }
+ }
+
+ public interface IOperandCapture : ITokenCapture
+ {
+ object Production { get; }
+ }
+
+ public class TokenEndOfList : IOperatorCapture, IOperandCapture
+ {
+ public string TokenId { get; }
+ public string Value { get; }
+ public bool IsFirst { get; }
+ public bool IsLast { get; }
+ public IEnumerable<ITokenCapture> LookAhead(params TokenGroup[] tokenIds)
+ {
+ return Items(this);
+ }
+
+ public IEnumerable<ITokenCapture> LookBehind(params TokenGroup[] tokenIds)
+ {
+ return Items(this);
+ }
+
+ public bool Is(params TokenGroup[] tokenIds)
+ {
+ return false;
+ }
+
+ public bool IsNot(params TokenGroup[] tokenIds)
+ {
+ return true;
+ }
+
+ public IOperandCapture Operand
+ {
+ get { return this; }
+ }
+
+ public IOperandCapture LeftOperand
+ {
+ get { return this; }
+ }
+
+ public IOperandCapture RightOperand
+ {
+ get { return this; }
+ }
+
+ public bool HasOperand { get; }
+ public bool HasLeftOperand { get; }
+ public bool HasRightOperand { get; }
+ public object Production { get; }
+ }
+
+ static TokenEndOfList EndOfList = new TokenEndOfList();
+ }
+}
diff --git a/src/qtvstools.regexpr/expression/Renderer.cs b/src/qtvstools.regexpr/expression/Renderer.cs
new file mode 100644
index 00000000..9a20d668
--- /dev/null
+++ b/src/qtvstools.regexpr/expression/Renderer.cs
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ public class Pattern
+ {
+ public RegExpr Expr { get; set; }
+ public string ExprRender { get; set; }
+ public Dictionary<string, Token> Tokens { get; set; }
+ }
+
+ class Renderer
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExpr.Renderer.RenderPattern()
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Transform the RegExpr representation of a regular expression into a pattern string
+ /// and a mapping of capture group id's into corresponding token definitions.
+ /// </summary>
+ /// <param name="rootExpr">RegExpr to render</param>
+ /// <param name="wsExpr">Default token white-space</param>
+ /// <returns>Pattern object containing pattern string and token map</returns>
+ public Pattern RenderPattern(RegExpr rootExpr, RegExpr wsExpr)
+ {
+ var pattern = new StringBuilder();
+ var tokens = new Dictionary<string, Token>
+ {
+ // Default root token
+ {"0", new Token("_ROOT", null) }
+ };
+ var stack = new Stack<StackFrame>();
+ var mode = RenderMode.Default;
+
+ stack.Push(rootExpr);
+ while (stack.Any()) {
+ var context = stack.Pop();
+
+ if (context.Expr == null)
+ continue;
+
+ var expr = context.Expr;
+ IEnumerable<RegExpr> children = context.Children;
+ RegExpr parent = stack.Any() ? stack.Peek() : null;
+
+ if (children == null) {
+ children = expr.OnRender(wsExpr, parent, pattern, ref mode);
+ if (children != null && children.Any()) {
+ stack.Push(new StackFrame { Expr = expr, Children = children.Skip(1) });
+ stack.Push(children.First());
+ }
+ } else if (children.Any()) {
+ expr.OnRenderNext(wsExpr, parent, pattern, ref mode);
+ stack.Push(new StackFrame { Expr = expr, Children = children.Skip(1) });
+ stack.Push(children.First());
+ } else {
+ expr.OnRenderEnd(wsExpr, parent, pattern, ref mode);
+ if (expr is Token)
+ tokens[expr.As<Token>().CaptureId] = expr.As<Token>();
+ }
+ }
+
+ return new Pattern
+ {
+ Expr = rootExpr,
+ ExprRender = pattern.ToString(),
+ Tokens = tokens
+ };
+ }
+
+ class StackFrame
+ {
+ public RegExpr Expr { get; set; }
+ public IEnumerable<RegExpr> Children { get; set; }
+
+ public static implicit operator StackFrame(RegExpr expr)
+ {
+ return new StackFrame { Expr = expr };
+ }
+
+ public static implicit operator RegExpr(StackFrame frame)
+ {
+ return (frame != null) ? frame.Expr : null;
+ }
+ }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/parser/ParseTree.cs b/src/qtvstools.regexpr/parser/ParseTree.cs
new file mode 100644
index 00000000..c90b41c8
--- /dev/null
+++ b/src/qtvstools.regexpr/parser/ParseTree.cs
@@ -0,0 +1,230 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExpr.ParseTree
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Result of processing input text with a pattern rendered from a RegExpr.
+ /// </summary>
+ /// <remarks>
+ /// Nodes in a ParseTree correspond to captured tokens. The parent-child relationship
+ /// between nodes reflects token embedding.
+ /// </remarks>
+ public class ParseTree
+ {
+ public Node Root { get; set; }
+ public const string KeyRoot = "ROOT";
+
+ public class Node : IOperatorCapture, IOperandCapture
+ {
+ public string CaptureId { get; set; }
+ public string Value { get; set; }
+ public int Begin { get; set; }
+ public int End { get; set; }
+ public int GroupIdx { get; set; }
+ public int CaptureIdx { get; set; }
+ public int OrderKey { get; set; }
+
+ public Token Token { get; set; }
+ public string TokenId { get { return Token.Id; } }
+
+ public object Production { get; set; }
+
+ public Node Parent { get; set; }
+
+ SortedList<int, Node> _ChildNodes = new SortedList<int, Node>();
+ public SortedList<int, Node> ChildNodes { get { return _ChildNodes; } }
+
+ ProductionObjects _ChildProductions = new ProductionObjects();
+ public ProductionObjects ChildProductions { get { return _ChildProductions; } }
+
+ public Queue<Node> TokenStream { get; set; }
+ public Stack<Node> OperatorStack { get; set; }
+ public Stack<Node> OperandStack { get; set; }
+
+ IProductionRule _Rule = null;
+ public IProductionRule Rule
+ {
+ get
+ {
+ if (_Rule == null)
+ _Rule = Token.SelectRule(this);
+ return _Rule;
+ }
+ }
+
+ public string Key
+ {
+ get
+ {
+ if (CaptureId == "0")
+ return KeyRoot;
+ return string.Format("{0}:{1}:{2}", CaptureId, Begin, End);
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0}[{1}]", TokenId, Value);
+ }
+
+ public static implicit operator ParseTree(Node node)
+ {
+ return new ParseTree { Root = node };
+ }
+
+ int SiblingIdx
+ {
+ get
+ {
+ if (Parent == null)
+ return 0;
+ return Parent.ChildNodes.IndexOfKey(OrderKey);
+ }
+ }
+
+ int SiblingCount
+ {
+ get
+ {
+ if (Parent == null)
+ return 1;
+ return Parent.ChildNodes.Count;
+ }
+ }
+
+ public bool IsFirst { get { return SiblingIdx == 0; } }
+
+ public bool IsLast { get { return SiblingIdx == SiblingCount - 1; } }
+
+ public IEnumerable<ITokenCapture> LookAhead(params TokenGroup[] ids)
+ {
+ if (Parent == null)
+ return Empty<ITokenCapture>();
+ var lookAhead = Parent.ChildNodes.Values
+ .Skip(SiblingIdx + 1);
+ if (ids.Any())
+ lookAhead = lookAhead.Where(x => ids.Any(g => g.Contains(x.TokenId)));
+ return lookAhead.Cast<ITokenCapture>().Concat(Items(EndOfList));
+ }
+
+ public IEnumerable<ITokenCapture> LookBehind(params TokenGroup[] ids)
+ {
+ if (Parent == null)
+ return Empty<ITokenCapture>();
+ var lookBehind = Parent.ChildNodes.Values
+ .Take(SiblingIdx)
+ .Reverse();
+ if (ids.Any())
+ lookBehind = lookBehind.Where(x => ids.Any(g => g.Contains(x.TokenId)));
+ return lookBehind.Cast<ITokenCapture>().Concat(Items(EndOfList));
+ }
+
+ public bool Is(params TokenGroup[] tokenIds)
+ {
+ return tokenIds.Any(g => g.Contains(TokenId));
+ }
+
+ public bool IsNot(params TokenGroup[] tokenIds)
+ {
+ return !tokenIds.Any(g => g.Contains(TokenId));
+ }
+
+ public IOperandCapture Operand
+ {
+ get
+ {
+ if (Parent == null)
+ return EndOfList;
+ if (Parent.OperandStack == null)
+ return EndOfList;
+ if (!Parent.OperandStack.Any())
+ return EndOfList;
+ return Parent.OperandStack.Peek();
+ }
+ }
+
+ public IOperandCapture LeftOperand
+ {
+ get
+ {
+ if (Parent == null)
+ return EndOfList;
+ if (Parent.OperandStack == null)
+ return EndOfList;
+ if (Parent.OperandStack.Count() < 2)
+ return EndOfList;
+ return Parent.OperandStack.Skip(1).First();
+ }
+ }
+
+ public IOperandCapture RightOperand
+ {
+ get
+ {
+ if (Parent == null)
+ return EndOfList;
+ if (Parent.OperandStack == null)
+ return EndOfList;
+ if (Parent.OperandStack.Count() < 2)
+ return EndOfList;
+ return Parent.OperandStack.Peek();
+ }
+ }
+
+ public bool HasOperand
+ {
+ get { return Operand != EndOfList; }
+ }
+
+ public bool HasLeftOperand
+ {
+ get { return LeftOperand != EndOfList; }
+ }
+
+ public bool HasRightOperand
+ {
+ get { return RightOperand != EndOfList; }
+ }
+ }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/parser/Parser.cs b/src/qtvstools.regexpr/parser/Parser.cs
new file mode 100644
index 00000000..3c59ea24
--- /dev/null
+++ b/src/qtvstools.regexpr/parser/Parser.cs
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExpr.Parser
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Rendering of <see cref="RegExpr"/>
+ /// </summary>
+ public partial class Parser
+ {
+ Renderer Renderer { get; set; }
+ Pattern Pattern { get; set; }
+ public Regex Regex { get; private set; }
+
+ internal Parser(RegExpr expr, RegExpr defaultTokenWs = null)
+ {
+ Renderer = new Renderer();
+ Refresh(expr, defaultTokenWs);
+ }
+
+ /// <summary>
+ /// Parse input text and return productions.
+ /// </summary>
+ /// <remarks>
+ /// The parsing procedure will first calculate the parse tree corresponding to the input
+ /// text, given the token data captured. The parse tree is then used to generate all
+ /// productions, according to the production rules defined for each token.
+ /// (see also <see cref="GetProductions(ParseTreeNode)"/>)
+ /// </remarks>
+ /// <param name="text">Text to be parsed.</param>
+ /// <returns>Productions by token id</returns>
+ public ProductionObjects Parse(string text)
+ {
+ var parseTree = GetParseTree(text);
+ return GetProductionObjects(parseTree);
+ }
+
+ public void Refresh(RegExpr expr, RegExpr defaultTokenWs = null)
+ {
+ // Render Regex string
+ Pattern = Renderer.RenderPattern(expr, defaultTokenWs);
+
+ // Compile Regex
+ Regex = new Regex(Pattern.ExprRender, RegexOptions.Multiline);
+ }
+
+ /// <summary>
+ /// Parse input text using Regex and generate corresponding parse tree.
+ /// </summary>
+ /// <param name="text">Text to be parsed</param>
+ /// <returns>Parse tree</returns>
+ ParseTree GetParseTree(string text)
+ {
+ // Match regex pattern
+ var match = Regex.Match(text);
+ if (!match.Success || match.Length == 0)
+ throw new ParseErrorException();
+
+ // Flat list of captures (parse-tree nodes)
+ var captures = match.Groups.Cast<Group>()
+ .SelectMany((g, gIdx) => g.Captures.Cast<Capture>()
+ .Where(c => !string.IsNullOrEmpty(c.Value))
+ .Select((c, cIdx) => new ParseTree.Node
+ {
+ CaptureId = Regex.GroupNameFromNumber(gIdx),
+ Token = Pattern.Tokens[ Regex.GroupNameFromNumber(gIdx)],
+ Value = c.Value,
+ Begin = c.Index,
+ End = c.Index + c.Length,
+ GroupIdx = gIdx,
+ CaptureIdx = cIdx,
+ }))
+ .OrderByDescending(c => c.Begin)
+ .ThenBy(c => c.End)
+ .ThenByDescending(c => c.GroupIdx)
+ .ThenByDescending(c => c.CaptureIdx);
+
+ // Capture index
+ var capture = captures
+ .GroupBy(x => x.Key)
+ .ToDictionary(x => x.Key, x => x.First());
+
+ // Parent(x) ::= smallest capture y, such that x is contained in y
+ var subCaptures = captures
+ .Select((x, xIdx) => new
+ {
+ IdxSelf = xIdx,
+ KeySelf = x.Key,
+ KeyParent = captures.Skip(xIdx + 1)
+ .SkipWhile(y => y.End < x.End)
+ .Select(y => y.Key)
+ .FirstOrDefault()
+ })
+ .Where(x => !string.IsNullOrEmpty(x.KeyParent));
+
+ // Link x <--> Parent(x)
+ foreach (var subCapture in subCaptures) {
+ var self = capture[subCapture.KeySelf];
+ var parent = capture[subCapture.KeyParent];
+ self.OrderKey = -subCapture.IdxSelf;
+ (self.Parent = parent).ChildNodes.Add(self.OrderKey, self);
+ }
+
+ // Return parse tree root
+ return capture[ParseTree.KeyRoot];
+ }
+ }
+
+ public class ParseErrorException : RegExpr.Exception
+ {
+ public ParseErrorException(string message = null) : base(message) { }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/production/Production.cs b/src/qtvstools.regexpr/production/Production.cs
new file mode 100644
index 00000000..540a82e3
--- /dev/null
+++ b/src/qtvstools.regexpr/production/Production.cs
@@ -0,0 +1,312 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ public partial class Parser
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// RegExpr.Parser.GetProductionObjects()
+ ///
+ ////////////////////////////////////////////////////////////////////////////////////////
+ /// <summary>
+ /// Extract productions from a parse tree.
+ /// </summary>
+ /// <param name="root">
+ /// Root node of parse tree, obtained from parsing an input text with a RegExpr
+ /// </param>
+ /// <returns>Productions by token id</returns>
+ ProductionObjects GetProductionObjects(ParseTree parseTree)
+ {
+ var outputProductions = new ProductionObjects();
+
+ var stack = new Stack<ParseTree.Node>();
+ stack.Push(parseTree.Root);
+ while (stack.Any()) {
+ var node = stack.Pop();
+
+ // Depth-first traversal
+ if (node.TokenStream == null) {
+ node.TokenStream = new Queue<ParseTree.Node>(node.ChildNodes.Values);
+ node.OperandStack = new Stack<ParseTree.Node>();
+ node.OperatorStack = new Stack<ParseTree.Node>();
+ stack.Push(node);
+ continue;
+ } else if (node.TokenStream.Any()) {
+ var nextNode = node.TokenStream.Dequeue();
+ stack.Push(node);
+ stack.Push(nextNode);
+ continue;
+ }
+
+ if (node.Parent == null)
+ continue;
+
+ var operatorStack = node.Parent.OperatorStack;
+ var operandStack = node.Parent.OperandStack;
+ var rule = node.Rule;
+ if (rule == null) {
+ // Default token (without rule definitions)
+ // just use captured value as production
+ node.Production = node.Value;
+ operandStack.Push(node);
+ } else if (rule.Delimiters != Delimiter.None) {
+ // Delimiter token
+ if (rule.Delimiters == Delimiter.Left) {
+ // if left delim, push to operator stack
+ operatorStack.Push(node);
+ } else {
+ // if right delim, unwind operator stack until left delim
+ UnwindOperatorStack(HaltUnwind.AtLeftDelimiter,
+ operatorStack, operandStack);
+ // set left delim as left operand, delimited expr as right operand
+ operandStack.ReverseTop();
+ // execute delimiter rule
+ node.Production = rule.Execute(node);
+ operandStack.Push(node);
+ }
+ } else if (rule.Operands != Operand.None) {
+ // Operator token
+ // unwind operator stack until lower priority operator or empty
+ UnwindOperatorStack(HaltUnwind.AtLowerPriority,
+ operatorStack, operandStack, rule.Priority);
+
+ // if operator needs left operand but none is available, error out
+ if (rule.Operands.HasFlag(Operand.Left) && !operandStack.Any())
+ throw new ParseErrorException();
+
+ if (rule.Operands.HasFlag(Operand.Right)) {
+ // if needs right operand, push to operator stack
+ operatorStack.Push(node);
+ } else {
+ // if left operand only, execute rule immediately
+ node.Production = rule.Execute(node);
+ operandStack.Push(node);
+ }
+ } else {
+ // Captured value or embedded captures ("nullary operator")
+ // execute rule immediately
+ node.Production = rule.Execute(node);
+ operandStack.Push(node);
+ }
+
+ if (node.IsLast) {
+ // Last token
+ // unwind operator stack until empty
+ UnwindOperatorStack(HaltUnwind.WhenEmpty,
+ operatorStack, operandStack);
+
+ // get output from operand stack
+ foreach (var operand in operandStack.Reverse()) {
+
+ // check if it's a dangling left delimiter
+ if (operand.Rule != null && operand.Rule.Delimiters == Delimiter.Left)
+ throw new ParseErrorException();
+
+ // add production to parent context
+ node.Parent.ChildProductions.Add(operand.TokenId, operand.Production);
+
+ // add production to output list
+ outputProductions.Add(operand.TokenId, operand.Production);
+ }
+ operandStack.Clear();
+ }
+ }
+
+ return outputProductions;
+ }
+
+ enum HaltUnwind
+ {
+ WhenEmpty,
+ AtLeftDelimiter,
+ AtLowerPriority
+ }
+
+ void UnwindOperatorStack(
+ HaltUnwind haltingCondition,
+ Stack<ParseTree.Node> operatorStack,
+ Stack<ParseTree.Node> operandStack,
+ int priority = int.MinValue)
+ {
+ while (operatorStack.Any()) {
+ var node = operatorStack.Pop();
+ Debug.Assert(node != null);
+
+ var rule = node.Rule;
+ Debug.Assert(rule != null);
+
+ if (haltingCondition == HaltUnwind.AtLeftDelimiter
+ && rule.Delimiters == Delimiter.Left
+ ) {
+ // Halting stack unwind: left delimiter found
+ // check if an operand (i.e. delimited expression) is available
+ if (!operandStack.Any())
+ throw new ParseErrorException();
+
+ // execute left delimiter rule
+ node.Production = rule.Execute(node);
+
+ // add to operands (will be picked up by right delimiter rule)
+ operandStack.Push(node);
+ return;
+ }
+
+ if (haltingCondition == HaltUnwind.AtLowerPriority
+ && rule.Priority < priority
+ ) {
+ // Halting stack unwind: lower priority operator found
+ // push operator back into stack
+ operatorStack.Push(node);
+ return;
+ }
+
+ // still haven't found what we're looking for; continue stack unwind
+ node.Production = rule.Execute(node);
+ operandStack.Push(node);
+ }
+
+ // error-out if didn't find left delimiter
+ if (haltingCondition == HaltUnwind.AtLeftDelimiter)
+ throw new ParseErrorException();
+ }
+ }
+
+ /// <summary>
+ /// Collection of production objects, grouped by token ID
+ /// </summary>
+ public partial class ProductionObjects : IEnumerable<KeyValuePair<string, object>>
+ {
+ List<KeyValuePair<string, object>> Productions { get; set; }
+ Dictionary<string, List<object>> ProductionsByTokenId { get; set; }
+
+ public ProductionObjects()
+ {
+ Productions = new List<KeyValuePair<string, object>>();
+ ProductionsByTokenId = new Dictionary<string, List<object>>();
+ }
+
+ public void Add(string tokenId, object prodObj)
+ {
+ Productions.Add(new KeyValuePair<string, object>(tokenId, prodObj));
+ List<object> prodObjs;
+ if (!ProductionsByTokenId.TryGetValue(tokenId, out prodObjs))
+ ProductionsByTokenId.Add(tokenId, prodObjs = new List<object>());
+ prodObjs.Add(prodObj);
+ }
+
+ public IEnumerable<T> GetValues<T>(string tokenId)
+ {
+ if (string.IsNullOrEmpty(tokenId))
+ return Empty<T>();
+
+ List<object> tokenProds;
+ if (!ProductionsByTokenId.TryGetValue(tokenId, out tokenProds))
+ return Empty<T>();
+
+ return tokenProds
+ .Where(x => x != null && x is T)
+ .Select(x => (T)x);
+ }
+
+ public IEnumerable<T> GetValues<T>(Enum tokenId)
+ {
+ return GetValues<T>(tokenId.ToString());
+ }
+
+ public IEnumerable<T> GetValues<T>(Token token)
+ {
+ return GetValues<T>(token.Id);
+ }
+
+ public IEnumerable<T> GetValues<T>(ProductionRule<T> production)
+ {
+ return GetValues<T>(production.Token.Id);
+ }
+
+ public IEnumerable<object> GetValues(string tokenId)
+ {
+ return GetValues<object>(tokenId);
+ }
+
+ public IEnumerable<object> GetValues(Enum tokenId)
+ {
+ return GetValues<object>(tokenId.ToString());
+ }
+
+ public IEnumerable<object> GetValues(Token token)
+ {
+ return GetValues<object>(token.Id);
+ }
+
+ public IEnumerable<object> GetValues(IProductionRule production)
+ {
+ return GetValues<object>(production.Token.Id);
+ }
+
+ public object this[string tokenId, int index = 0]
+ {
+ get
+ {
+ return GetValues(tokenId).ElementAtOrDefault(index);
+ }
+ }
+
+ public object this[Enum tokenId, int index = 0]
+ {
+ get
+ {
+ return this[tokenId.ToString(), index];
+ }
+ }
+
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ return ((IEnumerable<KeyValuePair<string, object>>)Productions).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable<KeyValuePair<string, object>>)Productions).GetEnumerator();
+ }
+ }
+ }
+}
diff --git a/src/qtvstools.regexpr/production/ProductionRule.cs b/src/qtvstools.regexpr/production/ProductionRule.cs
new file mode 100644
index 00000000..1f95b8fb
--- /dev/null
+++ b/src/qtvstools.regexpr/production/ProductionRule.cs
@@ -0,0 +1,409 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ [Flags]
+ public enum Delimiter { None, Left, Right }
+
+ [Flags]
+ public enum Operand { None, Left, Right }
+
+ public interface IProductionRule
+ {
+ int Priority { get; }
+ RuleCallback.Selector Selector { get; }
+ RuleCallback.PreCondition PreCondition { get; }
+ Token Token { get; set; }
+ Delimiter Delimiters { get; }
+ Operand Operands { get; }
+ object Execute(ParseTree.Node node);
+ }
+
+ public abstract class ProductionRule<T> : IProductionRule, IEnumerable
+ {
+ public int Priority { get; protected set; }
+ public RuleCallback.Selector Selector { get; set; }
+ public RuleCallback.PreCondition PreCondition { get; set; }
+
+ public Token Token { get; set; }
+ public virtual Delimiter Delimiters { get { return Delimiter.None; } }
+ public virtual Operand Operands { get { return Operand.None; } }
+
+ protected List<IRuleAction<T>> Actions = new List<IRuleAction<T>>();
+
+ protected void Init(
+ int priority, RuleCallback.Selector select, RuleCallback.PreCondition pre)
+ {
+ Priority = priority;
+ Selector = select;
+ PreCondition = pre;
+ }
+
+ public void Add(IRuleAction<T> action)
+ {
+ Actions.Add(action);
+ }
+
+ protected abstract object[] FetchOperands(Stack<ParseTree.Node> operandStack);
+
+ protected virtual T DefaultProduction(
+ string capturedValue,
+ Stack<ParseTree.Node> operandStack,
+ ProductionObjects productions)
+ {
+ return CreateInstance();
+ }
+
+ protected virtual bool TestPreCondition(
+ ParseTree.Node node,
+ string capturedValue,
+ Stack<ParseTree.Node> operandStack,
+ ProductionObjects productions)
+ {
+ if (PreCondition == null)
+ return true;
+ return PreCondition(node);
+ }
+
+ public object Execute(ParseTree.Node node)
+ {
+ if (PreCondition != null && !PreCondition(node))
+ throw new ParseErrorException();
+
+ if (node.Parent == null)
+ return null;
+
+ var operandStack = node.Parent.OperandStack;
+ var capturedValue = node.Value;
+ var productions = node.ChildProductions;
+
+ // pop-out rule operands from operand stack
+ object[] ruleOperands = FetchOperands(operandStack);
+
+ // calculate order of actions taking child productions into account
+ var actionScheduling = ScheduleActions(productions);
+
+ // run actions in the calculated order
+ T production = default(T);
+ foreach (var actionSchedule in actionScheduling) {
+ var a = actionSchedule.Action;
+ var childProduction = actionSchedule.Production;
+
+ // if production is null and action is update, init with default production
+ if (production == null && a.ActionInfo.ReturnType == typeof(void))
+ production = DefaultProduction(capturedValue, operandStack, productions);
+
+ // if action uses child production, use it as input instead of the rule operands
+ if (childProduction != null)
+ a.Execute(ref production, capturedValue, childProduction);
+ else
+ a.Execute(ref production, capturedValue, ruleOperands);
+ }
+
+ // if no production was created by rule actions, create default production
+ if (production == null)
+ production = DefaultProduction(capturedValue, operandStack, productions);
+
+ return production;
+ }
+
+ class ActionSchedule
+ {
+ public IRuleAction<T> Action { get; set; }
+ public int ActionIndex { get; set; }
+ public object Production { get; set; }
+ public int ProductionIndex { get; set; }
+ }
+
+ IEnumerable<ActionSchedule> ScheduleActions(ProductionObjects childProductions)
+ {
+ // actions with order of definition
+ var actionsOrder = Actions
+ .Select((a, aIdx) => new
+ {
+ Self = a,
+ Index = aIdx,
+ a.SourceTokenId
+ });
+ var dependentActions = actionsOrder
+ .Where(a => !string.IsNullOrEmpty(a.SourceTokenId));
+ var independentActions = actionsOrder
+ .Where(a => string.IsNullOrEmpty(a.SourceTokenId));
+
+ // child productions with order of creation
+ var productionsOrder = childProductions
+ .Select((p, pIdx) => new
+ {
+ Self = p.Value,
+ Index = pIdx,
+ TokenId = p.Key
+ });
+
+ // schedule actions that depend on child production:
+ // * actions x productions
+ // * sorted by production creation order (inverted)
+ // * and then by action definition order (inverted)
+ var dependentActionSchedules = dependentActions
+ .Join(productionsOrder,
+ a => a.SourceTokenId, p => p.TokenId,
+ (a, p) => new ActionSchedule
+ {
+ Action = a.Self,
+ ActionIndex = a.Index,
+ Production = p.Self,
+ ProductionIndex = p.Index,
+ })
+ .OrderByDescending(ap => ap.ProductionIndex)
+ .ThenByDescending(ap => ap.ActionIndex);
+
+ // insert independent actions in the right order
+ var scheduled = new Stack<ActionSchedule>();
+ IEnumerable<ActionSchedule> toSchedule = dependentActionSchedules;
+ foreach (var a in independentActions.OrderByDescending(a => a.Index)) {
+ var scheduleAfter = toSchedule
+ .TakeWhile(ap => ap.ActionIndex > a.Index);
+ foreach (var ap in scheduleAfter)
+ scheduled.Push(ap);
+ scheduled.Push(new ActionSchedule { Action = a.Self });
+
+ toSchedule = toSchedule.Skip(scheduleAfter.Count());
+ }
+ if (toSchedule.Any()) {
+ foreach (var ap in toSchedule)
+ scheduled.Push(ap);
+ }
+
+ return scheduled;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return Actions.GetEnumerator();
+ }
+
+ protected T CreateInstance()
+ {
+ var type = typeof(T);
+
+ if (type.IsValueType)
+ return default(T);
+
+ if (type == typeof(string))
+ return (T)(object)string.Empty;
+
+ if (!type.IsClass)
+ throw new InvalidOperationException("Not a class: " + type.Name);
+
+ if (type.IsAbstract)
+ throw new InvalidOperationException("Abstract class: " + type.Name);
+
+ if (type.ContainsGenericParameters)
+ throw new InvalidOperationException("Generic class: " + type.Name);
+
+ var ctorInfo = ((TypeInfo)type).DeclaredConstructors
+ .Where(x => x.GetParameters().Length == 0)
+ .FirstOrDefault();
+
+ if (ctorInfo == null)
+ throw new InvalidOperationException("No default constructor: " + type.Name);
+
+ return (T)ctorInfo.Invoke(new object[0]);
+ }
+ }
+
+ public class Rule<T> : ProductionRule<T>
+ {
+ public Rule(
+ int priority = int.MaxValue,
+ RuleCallback.Selector select = null,
+ RuleCallback.PreCondition pre = null)
+ {
+ Init(priority, select, pre);
+ }
+
+ protected override object[] FetchOperands(Stack<ParseTree.Node> operandStack)
+ {
+ return new object[] { };
+ }
+ }
+
+ public class PrefixRule<TOperand, T> : ProductionRule<T>
+ {
+ public override Operand Operands { get { return Operand.Right; } }
+
+ public PrefixRule(
+ int priority = 0,
+ RuleCallback.Selector select = null,
+ RuleCallback.PreCondition pre = null)
+ {
+ Init(priority, select, pre);
+ }
+
+ protected override object[] FetchOperands(Stack<ParseTree.Node> operandStack)
+ {
+ if (operandStack.Count < 1)
+ throw new ParseErrorException();
+
+ var operand = operandStack.Pop();
+ if (!(operand.Production is TOperand))
+ throw new ParseErrorException();
+
+ return new object[] { operand.Production };
+ }
+ }
+
+ public class PostfixRule<TOperand, T> : ProductionRule<T>
+ {
+ public override Operand Operands { get { return Operand.Left; } }
+
+ public PostfixRule(
+ int priority = 0,
+ RuleCallback.Selector select = null,
+ RuleCallback.PreCondition pre = null)
+ {
+ Init(priority, select, pre);
+ }
+
+ protected override object[] FetchOperands(Stack<ParseTree.Node> operandStack)
+ {
+ if (operandStack.Count < 1)
+ throw new ParseErrorException();
+
+ var operand = operandStack.Pop();
+ if (!(operand.Production is TOperand))
+ throw new ParseErrorException();
+
+ return new object[] { operand.Production };
+ }
+ }
+
+ public class InfixRule<TLeftOperand, TRightOperand, T> : ProductionRule<T>
+ {
+ public override Operand Operands { get { return Operand.Left | Operand.Right; } }
+
+ public InfixRule(
+ int priority = 0,
+ RuleCallback.Selector select = null,
+ RuleCallback.PreCondition pre = null)
+ {
+ Init(priority, select, pre);
+ }
+
+ protected override object[] FetchOperands(Stack<ParseTree.Node> operandStack)
+ {
+ if (operandStack.Count < 2)
+ throw new ParseErrorException();
+
+ var rightOperand = operandStack.Pop();
+ if (!(rightOperand.Production is TRightOperand))
+ throw new ParseErrorException();
+
+ var leftOperand = operandStack.Pop();
+ if (!(leftOperand.Production is TLeftOperand))
+ throw new ParseErrorException();
+
+ return new object[] { leftOperand.Production, rightOperand.Production };
+ }
+ }
+
+ public class LeftDelimiterRule<T> : ProductionRule<T>
+ {
+ public override Delimiter Delimiters { get { return Delimiter.Left; } }
+
+ public LeftDelimiterRule(
+ int priority = int.MinValue,
+ RuleCallback.Selector select = null,
+ RuleCallback.PreCondition pre = null)
+ {
+ Init(priority, select, pre);
+ }
+
+ protected override object[] FetchOperands(Stack<ParseTree.Node> operandStack)
+ {
+ return new object[] { };
+ }
+ }
+
+ public class RightDelimiterRule<TLeftDelim, TExpr, T> : ProductionRule<T>
+ {
+ public override Delimiter Delimiters { get { return Delimiter.Right; } }
+
+ public RightDelimiterRule(
+ int priority = int.MaxValue,
+ RuleCallback.Selector select = null,
+ RuleCallback.PreCondition pre = null)
+ {
+ Init(priority, select, pre);
+ }
+
+ protected override T DefaultProduction(
+ string capturedValue,
+ Stack<ParseTree.Node> operandStack,
+ ProductionObjects productions)
+ {
+ throw new ParseErrorException();
+ }
+
+ protected override object[] FetchOperands(Stack<ParseTree.Node> operandStack)
+ {
+ if (operandStack.Count < 2)
+ throw new ParseErrorException();
+
+ var delimitedExpr = operandStack.Pop();
+ if (!(delimitedExpr.Production is TExpr))
+ throw new ParseErrorException();
+
+ var leftDelimiter = operandStack.Pop();
+ if (!(leftDelimiter.Production is TLeftDelim))
+ throw new ParseErrorException();
+
+ return new object[] { leftDelimiter.Production, delimitedExpr.Production };
+ }
+ }
+
+ public static class RuleCallback
+ {
+ public delegate bool Selector(ITokenCapture token);
+ public delegate bool PreCondition(IOperatorCapture capture);
+ }
+
+ public const RuleCallback.Selector Default = null;
+ }
+}
diff --git a/src/qtvstools.regexpr/production/ProductionRuleAction.cs b/src/qtvstools.regexpr/production/ProductionRuleAction.cs
new file mode 100644
index 00000000..ab407fc0
--- /dev/null
+++ b/src/qtvstools.regexpr/production/ProductionRuleAction.cs
@@ -0,0 +1,345 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Reflection;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ public interface IRuleAction<T>
+ {
+ int NumOperands { get; }
+ string SourceTokenId { get; set; }
+ bool Execute(ref T productionObj, string capturedValue, params object[] operandObjs);
+ MethodInfo AssertInfo { get; }
+ MethodInfo ActionInfo { get; }
+ }
+
+ public class RuleAction<T, T1, T2> : IRuleAction<T>
+ {
+ public Delegate Assert { get; set; }
+ public Delegate Action { get; set; }
+
+ public MethodInfo AssertInfo { get { return Assert != null ? Assert.Method : null; } }
+ public MethodInfo ActionInfo { get { return Action != null ? Action.Method : null; } }
+
+ public string SourceTokenId { get; set; }
+
+ static readonly int _NumOperands
+ = (typeof(T1) != typeof(Void) ? 1 : 0)
+ + (typeof(T2) != typeof(Void) ? 1 : 0);
+
+ public int NumOperands { get { return _NumOperands; } }
+
+ bool TestAssert(T prod, string value, T1 x, T2 y)
+ {
+ if (Assert == null)
+ return true;
+
+ else if (Assert is CaptureCallback.Predicate)
+ return ((CaptureCallback.Predicate)Assert)(value);
+
+ else if (Assert is UnaryCallback.Predicate<T1>)
+ return ((UnaryCallback.Predicate<T1>)Assert)(x);
+
+ else if (Assert is UnaryCallback.Predicate<T, T1>)
+ return ((UnaryCallback.Predicate<T, T1>)Assert)(prod, x);
+
+ else if (Assert is BinaryCallback.Predicate<T1, T2>)
+ return ((BinaryCallback.Predicate<T1, T2>)Assert)(x, y);
+
+ else if (Assert is BinaryCallback.Predicate<T, T1, T2>)
+ return ((BinaryCallback.Predicate<T, T1, T2>)Assert)(prod, x, y);
+
+ else
+ throw new InvalidOperationException("Incompatible assert callback.");
+ }
+
+ void RunAction(ref T prod, string value, T1 x, T2 y)
+ {
+ if (Action == null)
+ throw new InvalidOperationException("Missing action callback.");
+
+ else if (Action is CaptureCallback.Create<T>)
+ prod = ((CaptureCallback.Create<T>)Action)(value);
+
+ else if (Action is UnaryCallback.Create<T, T1>)
+ prod = ((UnaryCallback.Create<T, T1>)Action)(x);
+
+ else if (Action is BinaryCallback.Create<T, T1, T2>)
+ prod = ((BinaryCallback.Create<T, T1, T2>)Action)(x, y);
+
+ else if (Action is UnaryCallback.Transform<T, T1>)
+ prod = ((UnaryCallback.Transform<T, T1>)Action)(prod, x);
+
+ else if (Action is BinaryCallback.Transform<T, T1, T2>)
+ prod = ((BinaryCallback.Transform<T, T1, T2>)Action)(prod, x, y);
+
+ else if (Action is UnaryCallback.Update<T, T1>)
+ ((UnaryCallback.Update<T, T1>)Action)(prod, x);
+
+ else if (Action is BinaryCallback.Update<T, T1, T2>)
+ ((BinaryCallback.Update<T, T1, T2>)Action)(prod, x, y);
+
+ else if (Action is UnaryCallback.Error<T, T1>)
+ throw new ErrorException(((UnaryCallback.Error<T, T1>)Action)(prod, x));
+
+ else if (Action is BinaryCallback.Error<T, T1, T2>)
+ throw new ErrorException(((BinaryCallback.Error<T, T1, T2>)Action)(prod, x, y));
+
+ else
+ throw new InvalidOperationException("Incompatible action callback.");
+ }
+
+ bool GetOperand<TOperand>(out TOperand x, object[] operands, ref int idx)
+ {
+ x = default(TOperand);
+ if (typeof(TOperand) == typeof(Void))
+ return true;
+ if (operands.Length <= idx)
+ return false;
+ object operandObj = operands[idx++];
+ if (!(operandObj is TOperand))
+ return false;
+ x = (TOperand)operandObj;
+ return true;
+ }
+
+ public bool Execute(ref T prod, string value, params object[] operands)
+ {
+ int idx = 0;
+ T1 x;
+ T2 y;
+ if (!GetOperand(out x, operands, ref idx))
+ return false;
+ if (!GetOperand(out y, operands, ref idx))
+ return false;
+
+ if (!TestAssert(prod, value, x, y))
+ return false;
+
+ RunAction(ref prod, value, x, y);
+ return true;
+ }
+ }
+
+ public class RuleAction<T, T1> : RuleAction<T, T1, Void> { }
+
+ public class RuleAction<T> : RuleAction<T, Void, Void> { }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ #region Capture
+
+ public static RuleAction<T> Capture<T>(
+ CaptureCallback.Predicate p, CaptureCallback.Create<T> a)
+ { return new RuleAction<T> { Assert = p, Action = a }; }
+
+ public static RuleAction<T> Capture<T>(
+ CaptureCallback.Create<T> a)
+ { return new RuleAction<T> { Assert = null, Action = a }; }
+
+ #endregion Capture
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ #region Create
+
+ public static RuleAction<T, T1> Create<T, T1>(
+ string sid, UnaryCallback.Predicate<T1> p, UnaryCallback.Create<T, T1> a)
+ { return new RuleAction<T, T1> { SourceTokenId = sid, Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1> Create<T, T1>(
+ Enum sid, UnaryCallback.Predicate<T1> p, UnaryCallback.Create<T, T1> a)
+ { return Create(sid.ToString(), p, a); }
+
+ public static RuleAction<T, T1> Create<T, T1>(
+ string sid, UnaryCallback.Create<T, T1> a)
+ { return Create(sid, null, a); }
+
+ public static RuleAction<T, T1> Create<T, T1>(
+ Enum sid, UnaryCallback.Create<T, T1> a)
+ { return Create(sid, null, a); }
+
+ public static RuleAction<T, T1> Create<T, T1>(
+ UnaryCallback.Predicate<T1> p, UnaryCallback.Create<T, T1> a)
+ { return Create(string.Empty, p, a); }
+
+ public static RuleAction<T, T1> Create<T, T1>(
+ UnaryCallback.Create<T, T1> a)
+ { return Create(string.Empty, null, a); }
+
+ public static RuleAction<T, T1, T2> Create<T, T1, T2>(
+ BinaryCallback.Predicate<T1, T2> p, BinaryCallback.Create<T, T1, T2> a)
+ { return new RuleAction<T, T1, T2> { Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1, T2> Create<T, T1, T2>(
+ BinaryCallback.Create<T, T1, T2> a)
+ { return Create(null, a); }
+
+ #endregion Create
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ #region Transform
+
+ public static RuleAction<T, T1> Transform<T, T1>(
+ string sid, UnaryCallback.Predicate<T, T1> p, UnaryCallback.Transform<T, T1> a)
+ { return new RuleAction<T, T1> { SourceTokenId = sid, Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1> Transform<T, T1>(
+ Enum sid, UnaryCallback.Predicate<T, T1> p, UnaryCallback.Transform<T, T1> a)
+ { return Transform(sid.ToString(), p, a); }
+
+ public static RuleAction<T, T1> Transform<T, T1>(
+ string sid, UnaryCallback.Transform<T, T1> a)
+ { return Transform(sid, null, a); }
+
+ public static RuleAction<T, T1> Transform<T, T1>(
+ Enum sid, UnaryCallback.Transform<T, T1> a)
+ { return Transform(sid, null, a); }
+
+ public static RuleAction<T, T1> Transform<T, T1>(
+ UnaryCallback.Transform<T, T1> a)
+ { return Transform(string.Empty, null, a); }
+
+ public static RuleAction<T, T1, T2> Transform<T, T1, T2>(
+ BinaryCallback.Predicate<T, T1, T2> p, BinaryCallback.Transform<T, T1, T2> a)
+ { return new RuleAction<T, T1, T2> { Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1, T2> Transform<T, T1, T2>(
+ BinaryCallback.Transform<T, T1, T2> a)
+ { return Transform(null, a); }
+
+ #endregion Transform
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ #region Update
+
+ public static RuleAction<T, T1> Update<T, T1>(
+ string sid, UnaryCallback.Predicate<T, T1> p, UnaryCallback.Update<T, T1> a)
+ { return new RuleAction<T, T1> { SourceTokenId = sid, Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1> Update<T, T1>(
+ Enum sid, UnaryCallback.Predicate<T, T1> p, UnaryCallback.Update<T, T1> a)
+ { return Update(sid.ToString(), p, a); }
+
+ public static RuleAction<T, T1> Update<T, T1>(
+ string sid, UnaryCallback.Update<T, T1> a)
+ { return Update(sid, null, a); }
+
+ public static RuleAction<T, T1> Update<T, T1>(
+ Enum sid, UnaryCallback.Update<T, T1> a)
+ { return Update(sid, null, a); }
+
+ public static RuleAction<T, T1> Update<T, T1>(
+ UnaryCallback.Update<T, T1> a)
+ { return Update(string.Empty, null, a); }
+
+ public static RuleAction<T, T1, T2> Update<T, T1, T2>(
+ BinaryCallback.Predicate<T, T1, T2> p, BinaryCallback.Update<T, T1, T2> a)
+ { return new RuleAction<T, T1, T2> { Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1, T2> Update<T, T1, T2>(
+ BinaryCallback.Update<T, T1, T2> a)
+ { return Update(null, a); }
+
+ #endregion Update
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ #region Error
+
+ public static RuleAction<T, T1> Error<T, T1>(
+ string sid, UnaryCallback.Predicate<T, T1> p, UnaryCallback.Error<T, T1> a)
+ { return new RuleAction<T, T1> { SourceTokenId = sid, Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1> Error<T, T1>(
+ Enum sid, UnaryCallback.Predicate<T, T1> p, UnaryCallback.Error<T, T1> a)
+ { return Error(sid.ToString(), p, a); }
+
+ public static RuleAction<T, T1> Error<T, T1>(
+ string sid, UnaryCallback.Error<T, T1> a)
+ { return Error(sid, null, a); }
+
+ public static RuleAction<T, T1> Error<T, T1>(
+ Enum sid,
+ UnaryCallback.Error<T, T1> a)
+ { return Error(sid, null, a); }
+
+ public static RuleAction<T, T1> Error<T, T1>(
+ UnaryCallback.Predicate<T, T1> p, UnaryCallback.Error<T, T1> a)
+ { return Error(string.Empty, p, a); }
+
+ public static RuleAction<T, T1> Error<T, T1>(
+ UnaryCallback.Error<T, T1> a)
+ { return Error(string.Empty, null, a); }
+
+ public static RuleAction<T, T1, T2> Error<T, T1, T2>(
+ BinaryCallback.Predicate<T, T1, T2> p, BinaryCallback.Error<T, T1, T2> a)
+ { return new RuleAction<T, T1, T2> { Assert = p, Action = a }; }
+
+ public static RuleAction<T, T1, T2> Error<T, T1, T2>(
+ BinaryCallback.Error<T, T1, T2> a)
+ { return Error(null, a); }
+
+ public class ErrorException : RegExpr.Exception
+ { public ErrorException(string message = null) : base(message) { } }
+
+ #endregion Error
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ #region Callbacks
+
+ public static class CaptureCallback
+ {
+ public delegate bool Predicate(string capture);
+ public delegate T Create<T>(string capture);
+ }
+
+ public static class UnaryCallback
+ {
+ public delegate bool Predicate<T1>(T1 operand1);
+ public delegate bool Predicate<T, T1>(T obj, T1 operand1);
+ public delegate T Create<T, T1>(T1 operand1);
+ public delegate T Transform<T, T1>(T obj, T1 operand1);
+ public delegate void Update<T, T1>(T obj, T1 operand1);
+ public delegate string Error<T, T1>(T obj, T1 operand1);
+ }
+
+ public static class BinaryCallback
+ {
+ public delegate bool Predicate<T1, T2>(T1 operand1, T2 operand2);
+ public delegate bool Predicate<T, T1, T2>(T obj, T1 operand1, T2 operand2);
+ public delegate T Create<T, T1, T2>(T1 operand1, T2 operand2);
+ public delegate T Transform<T, T1, T2>(T obj, T1 operand1, T2 operand2);
+ public delegate void Update<T, T1, T2>(T obj, T1 operand1, T2 operand2);
+ public delegate string Error<T, T1, T2>(T obj, T1 operand1, T2 operand2);
+ }
+
+ #endregion Callbacks
+ }
+}
diff --git a/src/qtvstools.regexpr/utils/Consts.cs b/src/qtvstools.regexpr/utils/Consts.cs
new file mode 100644
index 00000000..cecd85c0
--- /dev/null
+++ b/src/qtvstools.regexpr/utils/Consts.cs
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ using static CharClass;
+
+ public abstract partial class RegExpr
+ {
+ /// <summary><![CDATA[
+ /// Equivalent to: [\w]
+ /// ]]></summary>
+ public static CharClassLiteral CharWord
+ { get { return new CharClassLiteral { LiteralChars = @"\w" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: [\r]
+ /// ]]></summary>
+ public static CharClassLiteral CharCr
+ { get { return new CharClassLiteral { LiteralChars = @"\r" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: [\n]
+ /// ]]></summary>
+ public static CharClassLiteral CharLf
+ { get { return new CharClassLiteral { LiteralChars = @"\n" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: [\s]
+ /// ]]></summary>
+ public static CharClassLiteral CharSpace
+ { get { return new CharClassLiteral { LiteralChars = @"\s" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: [\S]
+ /// ]]></summary>
+ public static CharClassLiteral CharNonSpace
+ { get { return new CharClassLiteral { LiteralChars = @"\S" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: [\r\n]
+ /// ]]></summary>
+ public static CharClassSet CharVertSpace
+ { get { return CharSet[CharCr + CharLf]; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: [^\S\r\n]
+ /// ]]></summary>
+ public static CharClassSet CharHorizSpace
+ { get { return CharSet[~(CharNonSpace + CharVertSpace)]; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: .
+ /// ]]></summary>
+ public static RegExprLiteral AnyChar
+ { get { return new RegExprLiteral { LiteralExpr = "." }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: ^
+ /// ]]></summary>
+ public static RegExprLiteral StartOfLine
+ { get { return new RegExprLiteral { LiteralExpr = "^" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: $
+ /// ]]></summary>
+ public static RegExprLiteral EndOfLine
+ { get { return new RegExprLiteral { LiteralExpr = "$" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: \A
+ /// ]]></summary>
+ public static RegExprLiteral StartOfFile
+ { get { return new RegExprLiteral { LiteralExpr = @"\A" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: \z
+ /// ]]></summary>
+ public static RegExprLiteral EndOfFile
+ { get { return new RegExprLiteral { LiteralExpr = @"\z" }; } }
+
+ /// <summary><![CDATA[
+ /// Equivalent to: \r?\n
+ /// ]]></summary>
+ public static RegExprSequence LineBreak
+ { get { return CharCr.Optional() & CharLf; } }
+
+ /// <summary>
+ /// Applies the same whitespace skipping rules as tokens, but does not any capture text.
+ /// </summary>
+ public static RegExpr SkipWs
+ { get { return new Token(); } }
+
+ static CharExprBuilder _Char = new CharExprBuilder();
+ public static CharExprBuilder Char { get { return _Char; } }
+
+ static CharSetExprBuilder _CharSet = new CharSetExprBuilder();
+ public static CharSetExprBuilder CharSet { get { return _CharSet; } }
+
+ static CharSetRawExprBuilder _CharSetRaw = new CharSetRawExprBuilder();
+ public static CharSetRawExprBuilder CharSetRaw { get { return _CharSetRaw; } }
+
+ static AssertExprBuilder _LookAhead = new AssertExprBuilder(AssertLookAhead);
+ public static AssertExprBuilder LookAhead { get { return _LookAhead; } }
+
+ static AssertExprBuilder _LookBehind = new AssertExprBuilder(AssertLookBehind);
+ public static AssertExprBuilder LookBehind { get { return _LookBehind; } }
+
+ public const SkipWhitespace SkipWs_Disable = SkipWhitespace.Disable;
+ }
+}
diff --git a/src/qtvstools.regexpr/utils/Utils.cs b/src/qtvstools.regexpr/utils/Utils.cs
new file mode 100644
index 00000000..bce68583
--- /dev/null
+++ b/src/qtvstools.regexpr/utils/Utils.cs
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace QtVsTools.SyntaxAnalysis
+{
+ public abstract partial class RegExpr
+ {
+ const string MetaChars = @"[]\/#^$.|?*+(){}-";
+
+ protected static string Escape(string s)
+ {
+ return new string(s.SelectMany(c =>
+ MetaChars.Contains(c) ? Items('\\', c) :
+ (c == ' ') ? "\\x20".Cast<char>() :
+ (c == '\t') ? "\\t".Cast<char>() :
+ (c == '\r') ? "\\r".Cast<char>() :
+ (c == '\n') ? "\\n".Cast<char>() :
+ Items(c)).ToArray());
+ }
+
+ public static bool NeedsGroup(string literal)
+ {
+ if (literal.Length == 1)
+ return false;
+ if (literal.Length == 2 && literal.StartsWith(@"\"))
+ return false;
+ if (literal.Length == 3 && literal.StartsWith(@"\c"))
+ return false;
+ if (literal.Length == 4 && literal.StartsWith(@"\x"))
+ return false;
+ if (literal.Length == 6 && literal.StartsWith(@"\u"))
+ return false;
+ return true;
+ }
+
+ internal T As<T>()
+ where T : RegExpr
+ {
+ if (this is T)
+ return this as T;
+ else
+ throw new InvalidCastException();
+ }
+
+ internal static IEnumerable<T> Empty<T>()
+ {
+ return new T[] { };
+ }
+
+ internal static IEnumerable<T> Items<T>(params T[] items)
+ {
+ return items;
+ }
+
+ public sealed class Void { }
+ }
+
+ public static class RegExprExtensions
+ {
+ public static void ReverseTop<T>(this Stack<T> stack, int count = 2)
+ {
+ if (count < 2 || stack.Count < count)
+ return;
+ var items = Enumerable.Repeat(stack, count).Select(s => s.Pop()).ToList();
+ items.ForEach(item => stack.Push(item));
+ }
+ }
+}
diff --git a/src/tests/Test_QtVsTools.RegExpr/Properties/AssemblyInfo.cs b/src/tests/Test_QtVsTools.RegExpr/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..e03b1e51
--- /dev/null
+++ b/src/tests/Test_QtVsTools.RegExpr/Properties/AssemblyInfo.cs
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Test_QtVsTools.RegExpr")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Test_QtVsTools.RegExpr")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("d574efed-5e19-45be-9b05-310f65065303")]
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/tests/Test_QtVsTools.RegExpr/Test_MacroParser.cs b/src/tests/Test_QtVsTools.RegExpr/Test_MacroParser.cs
new file mode 100644
index 00000000..d23dd75e
--- /dev/null
+++ b/src/tests/Test_QtVsTools.RegExpr/Test_MacroParser.cs
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace QtVsTools.Test.RegExpr
+{
+ using QtVsTest.Macros;
+
+ [TestClass]
+ public class Test_MacroParser
+ {
+ MacroParser Parser = MacroParser.Get();
+
+ [TestMethod]
+ public void TestMacro()
+ {
+ string testInput =
+ "//# wait 15000 Assembly QtVsTools => GetAssembly(\"QtVsTools\")" + "\r\n" +
+ "var VsixType = QtVsTools.GetType(\"QtVsTools.Vsix\");" + "\r\n" +
+ "var VsixInstance = VsixType.GetProperty(\"Instance\"," + "\r\n" +
+ " BindingFlags.Public | BindingFlags.Static);" + "\r\n" +
+ "//# wait 15000 object Vsix => VsixInstance.GetValue(null)" + "\r\n" +
+ "Result = \"(ok)\";";
+
+ var macroLines = Parser.Parse(testInput);
+
+ Debug.Assert(macroLines != null);
+ Debug.Assert(macroLines.Count() == 6);
+ Debug.Assert(macroLines.Skip(0).First() is Statement);
+ Debug.Assert(macroLines.Skip(1).First() is CodeLine);
+ Debug.Assert(macroLines.Skip(2).First() is CodeLine);
+ Debug.Assert(macroLines.Skip(3).First() is CodeLine);
+ Debug.Assert(macroLines.Skip(4).First() is Statement);
+ Debug.Assert(macroLines.Skip(5).First() is CodeLine);
+ }
+ }
+}
diff --git a/src/tests/Test_QtVsTools.RegExpr/Test_QtVsTools.RegExpr.csproj b/src/tests/Test_QtVsTools.RegExpr/Test_QtVsTools.RegExpr.csproj
new file mode 100644
index 00000000..9f5eb785
--- /dev/null
+++ b/src/tests/Test_QtVsTools.RegExpr/Test_QtVsTools.RegExpr.csproj
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="$(VisualStudioVersion)" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<!--
+ *****************************************************************************
+ **
+ ** Copyright (C) 2019 The Qt Company Ltd.
+ ** Contact: https://www.qt.io/licensing/
+ **
+ ** This file is part of the Qt VS Tools.
+ **
+ ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+ ** Commercial License Usage
+ ** Licensees holding valid commercial Qt licenses may use this file in
+ ** accordance with the commercial license agreement provided with the
+ ** Software or, alternatively, in accordance with the terms contained in
+ ** a written agreement between you and The Qt Company. For licensing terms
+ ** and conditions see https://www.qt.io/terms-conditions. For further
+ ** information use the contact form at https://www.qt.io/contact-us.
+ **
+ ** GNU General Public License Usage
+ ** Alternatively, this file may be used under the terms of the GNU
+ ** General Public License version 3 as published by the Free Software
+ ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+ ** included in the packaging of this file. Please review the following
+ ** information to ensure the GNU General Public License requirements will
+ ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+ **
+ ** $QT_END_LICENSE$
+ **
+ *****************************************************************************
+-->
+
+ <Import Project="..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props')" />
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{D574EFED-5E19-45BE-9B05-310F65065303}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Test_QtVsTools.RegExpr</RootNamespace>
+ <AssemblyName>Test_QtVsTools.RegExpr</AssemblyName>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
+ <IsCodedUITest>False</IsCodedUITest>
+ <TestProjectType>UnitTest</TestProjectType>
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\..\qtvstest\MacroParser.cs">
+ <Link>MacroParser.cs</Link>
+ </Compile>
+ <Compile Include="Test_MacroParser.cs" />
+ <Compile Include="Test_XmlIntParser.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\QtVsTools.RegExpr\QtVsTools.RegExpr.csproj">
+ <Project>{0bdf77d1-4705-402c-8e58-f0d4d2679c08}</Project>
+ <Name>QtVsTools.RegExpr</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+ <PropertyGroup>
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+ </PropertyGroup>
+ <Error Condition="!Exists('..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props'))" />
+ <Error Condition="!Exists('..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets'))" />
+ </Target>
+ <Import Project="..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets')" />
+</Project> \ No newline at end of file
diff --git a/src/tests/Test_QtVsTools.RegExpr/Test_XmlIntParser.cs b/src/tests/Test_QtVsTools.RegExpr/Test_XmlIntParser.cs
new file mode 100644
index 00000000..5afae5ac
--- /dev/null
+++ b/src/tests/Test_QtVsTools.RegExpr/Test_XmlIntParser.cs
@@ -0,0 +1,344 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt VS Tools.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace QtVsTools.Test.RegExpr
+{
+ using static SyntaxAnalysis.RegExpr;
+
+ [TestClass]
+ public class Test_XmlIntParser
+ {
+ public const string IdNum = "NUM";
+ public const string IdExpr = "EXPR";
+ public const string IdExprLPar = "EXPR_LPAR";
+ public const string IdTagValue = "TAG_VALUE";
+ public const string IdTagBegin = "TAG_BEGIN";
+ public const string IdTagName = "TAG_NAME";
+ public const string IdTag = "TAG";
+
+ public static Parser GetParser()
+ {
+ // XML chars
+ var charLt = Char['<'];
+ var charGt = Char['>'];
+ var charSlash = Char['/'];
+
+ // int expr chars
+ var charPlus = Char['+'];
+ var charMinus = Char['-'];
+ var charMult = Char['*'];
+ var charDiv = Char['/'];
+ var charLPar = Char['('];
+ var charRPar = Char[')'];
+ var charDigit = Char['0', '9'];
+ var charAddtOper = CharSet[charPlus + charMinus];
+ var charMultOper = CharSet[charMult + charDiv];
+ var charOper = CharSet[charAddtOper + charMultOper];
+
+ // int operator priorities
+ int priorityInfixAddt = 10;
+ int priorityInfixMult = 20;
+ int priorityPrefixAddt = 30;
+
+ // token: number
+ var exprNum = new Token(IdNum,
+ charDigit.Repeat() & !LookAhead[SkipWs & (charDigit | charLPar)])
+ {
+ new Rule<int>
+ {
+ Capture(value => int.Parse(value))
+ }
+ };
+
+ // token: plus (infix or prefix)
+ var exprPlus = new Token(IdExpr,
+ charPlus & !LookAhead[SkipWs & (charOper | charRPar | charLt)])
+ {
+ new PrefixRule<int, int>(
+ priority: priorityPrefixAddt,
+ select: t => (t.IsFirst || t.LookBehind().First().Is(IdExprLPar))
+ && t.LookAhead().First().Is(IdNum, IdExprLPar))
+ {
+ Create((int x) => +x)
+ },
+
+ new InfixRule<int, int, int>(priority: priorityInfixAddt)
+ {
+ Create((int x, int y) => x + y)
+ }
+ };
+
+ // token: minus (infix or prefix)
+ var exprMinus = new Token(IdExpr,
+ charMinus & !LookAhead[SkipWs & (charOper | charRPar | charLt)])
+ {
+ new PrefixRule<int, int>(
+ priority: priorityPrefixAddt,
+ select: t => (t.IsFirst || t.LookBehind().First().Is(IdExprLPar))
+ && t.LookAhead().First().Is(IdNum, IdExprLPar))
+ {
+ Create((int x) => -x)
+ },
+
+ new InfixRule<int, int, int>(priority: priorityInfixAddt)
+ {
+ Create((int x, int y) => x - y)
+ }
+ };
+
+ // token: multiplication
+ var exprMult = new Token(IdExpr,
+ charMult & !LookAhead[SkipWs & (charOper | charRPar | charLt)])
+ {
+ new InfixRule<int, int, int>(priority: priorityInfixMult)
+ {
+ Create((int x, int y) => x * y)
+ }
+ };
+
+ // token: division
+ var exprDiv = new Token(IdExpr,
+ charDiv & !LookAhead[SkipWs & (charOper | charRPar | charLt)])
+ {
+ new InfixRule<int, int, int>(priority: priorityInfixMult)
+ {
+ Create((int x, int y) => x / y)
+ }
+ };
+
+ // token: left parenthesis
+ var exprLPar = new Token(IdExprLPar,
+ charLPar & !LookAhead[SkipWs & (charRPar | charLt)])
+ {
+ new LeftDelimiterRule<string>()
+ {
+ Capture(value => value)
+ }
+ };
+
+ // token: right parenthesis
+ var exprRPar = new Token(IdExpr,
+ charRPar & !LookAhead[SkipWs & (charDigit | charLPar)])
+ {
+ new RightDelimiterRule<string, int, int>
+ {
+ Create((string lPar, int n) => n)
+ }
+ };
+
+ // int expression
+ var numExpr = (exprNum
+ | exprPlus
+ | exprMinus
+ | exprMult
+ | exprDiv
+ | exprLPar
+ | exprRPar).Repeat();
+
+ // token: tag value containing int expression
+ var tagValue = new Token(IdTagValue, SkipWs_Disable,
+ LookAhead[SkipWs & CharSet[~(CharSpace + charLt)]] & numExpr & LookAhead[charLt])
+ {
+ new Rule<string>
+ {
+ Create(IdNum, (int expr) => "=" + expr.ToString()),
+ Create(IdExpr, (int expr) => "=" + expr.ToString())
+ }
+ };
+
+ // token: tag open (only tag name, no attribs)
+ var tagBegin = new Token(IdTagBegin,
+ charLt & new Token(IdTagName, CharWord.Repeat()) & charGt)
+ {
+ new LeftDelimiterRule<string>
+ {
+ Create(IdTagName, (string tagName) => tagName)
+ }
+ };
+
+ // token: tag close
+ var tagEnd = new Token(IdTag,
+ charLt & charSlash & new Token(IdTagName, CharWord.Repeat()) & LookAhead[charGt])
+ {
+ new RightDelimiterRule<string, string, string>
+ {
+ Create(IdTagName, (string name) => name),
+ Error(
+ (string tag, string tagName) => tagName != tag,
+ (tag, tagName) => string.Format("Expected {0}, found {1}", tagName, tag)),
+ Create(
+ (string tag, string value) => value.StartsWith("="),
+ (tag, value) => tag + value),
+ Create(
+ (string tag, string value) => !value.StartsWith("="),
+ (tag, value) => tag + ":{" + value + "}")
+ }
+ };
+
+ // token: tag sequence
+ var tagConcat = new Token(IdTag, charGt & LookAhead[SkipWs & charLt & ~charSlash])
+ {
+ new InfixRule<string, string, string>(
+ pre: t => t.LeftOperand.Is(IdTag) && t.RightOperand.Is(IdTag))
+ {
+ Create((string leftTag, string rightTag) => leftTag + "," + rightTag)
+ }
+ };
+
+ // XML containing int expressions
+ var xmlInt = StartOfLine
+ & (tagBegin | tagValue | tagEnd & (tagConcat | charGt)).Repeat()
+ & SkipWs & EndOfFile;
+
+ // generate RegExpr parser
+ return xmlInt.Render(CharSpace.Repeat());
+ }
+
+ Parser Parser = GetParser();
+
+ [TestMethod]
+ public void TestConst()
+ {
+ string testInput = "<x>42</x>";
+ string testOutput = "x=42";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ParseErrorException))]
+ public void TestConstError()
+ {
+ string testInput = "<x>foo</x>";
+ Parser.Parse(testInput);
+ }
+
+ [TestMethod]
+ public void TestInfix()
+ {
+ string testInput = "<x>2 - 1</x>";
+ string testOutput = "x=1";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ParseErrorException))]
+ public void TestInfixError()
+ {
+ string testInput = "<x>2 - </x>";
+ Parser.Parse(testInput);
+ }
+
+ [TestMethod]
+ public void TestPrefix()
+ {
+ string testInput = "<x>-2 + 1</x>";
+ string testOutput = "x=-1";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+ [TestMethod]
+ [ExpectedException(typeof(ParseErrorException))]
+ public void TestPrefixError()
+ {
+ string testInput = "<x>- + 1</x>";
+ Parser.Parse(testInput);
+ }
+
+ [TestMethod]
+ public void TestPrecedence()
+ {
+ string testInput = "<x>2 + 3 * 4</x>";
+ string testOutput = "x=14";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+
+ [TestMethod]
+ public void TestParentheses()
+ {
+ string testInput = "<x>(2 + 3) * 4</x>";
+ string testOutput = "x=20";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ParseErrorException))]
+ public void TestParenthesesLeftError()
+ {
+ string testInput = "<x>2 + 3) * 4</x>";
+ Parser.Parse(testInput);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ParseErrorException))]
+ public void TestParenthesesRightError()
+ {
+ string testInput = "<x>(2 + 3 * 4</x>";
+ Parser.Parse(testInput);
+ }
+
+ [TestMethod]
+ public void TestParenthesesNested()
+ {
+ string testInput = "<x>(-((2 + 3) * 4) / 5) * 3</x>";
+ string testOutput = "x=-12";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+
+ [TestMethod]
+ public void TestNestedTags()
+ {
+ string testInput = "<a><x>(-((2 + 3) * 4) / 5) * 3</x><y>(2 + 3) * 4</y></a>";
+ string testOutput = "a:{x=-12,y=20}";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ParseErrorException))]
+ public void TestNestedTagsError()
+ {
+ string testInput = "<a><x>1</x><y>2<z><w>";
+ Parser.Parse(testInput);
+ }
+
+ [TestMethod]
+ public void TestMultiLines()
+ {
+ string testInput =
+ "<a>" + "\r\n" +
+ " <x>2 + 3 * 4</x>" + "\r\n" +
+ " <y>(2 + 3) * 4</y>" + "\r\n" +
+ "</a>";
+ string testOutput = "a:{x=14,y=20}";
+ Debug.Assert(Parser.Parse(testInput).GetValues<string>(IdTag).First() == testOutput);
+ }
+ }
+}
diff --git a/src/tests/Test_QtVsTools.RegExpr/packages.config b/src/tests/Test_QtVsTools.RegExpr/packages.config
new file mode 100644
index 00000000..e5ca672b
--- /dev/null
+++ b/src/tests/Test_QtVsTools.RegExpr/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="MSTest.TestAdapter" version="1.3.2" targetFramework="net452" />
+ <package id="MSTest.TestFramework" version="1.3.2" targetFramework="net452" />
+</packages> \ No newline at end of file