| /****************************************************************************  | 
| **  | 
| ** 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);  | 
|         }  | 
|     }  | 
| }  |