/**************************************************************************** ** ** 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 { Capture(value => int.Parse(value)) } }; // token: plus (infix or prefix) var exprPlus = new Token(IdExpr, charPlus & !LookAhead[SkipWs & (charOper | charRPar | charLt)]) { new PrefixRule( priority: priorityPrefixAddt, select: t => (t.IsFirst || t.LookBehind().First().Is(IdExprLPar)) && t.LookAhead().First().Is(IdNum, IdExprLPar)) { Create((int x) => +x) }, new InfixRule(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( priority: priorityPrefixAddt, select: t => (t.IsFirst || t.LookBehind().First().Is(IdExprLPar)) && t.LookAhead().First().Is(IdNum, IdExprLPar)) { Create((int x) => -x) }, new InfixRule(priority: priorityInfixAddt) { Create((int x, int y) => x - y) } }; // token: multiplication var exprMult = new Token(IdExpr, charMult & !LookAhead[SkipWs & (charOper | charRPar | charLt)]) { new InfixRule(priority: priorityInfixMult) { Create((int x, int y) => x * y) } }; // token: division var exprDiv = new Token(IdExpr, charDiv & !LookAhead[SkipWs & (charOper | charRPar | charLt)]) { new InfixRule(priority: priorityInfixMult) { Create((int x, int y) => x / y) } }; // token: left parenthesis var exprLPar = new Token(IdExprLPar, charLPar & !LookAhead[SkipWs & (charRPar | charLt)]) { new LeftDelimiterRule() { Capture(value => value) } }; // token: right parenthesis var exprRPar = new Token(IdExpr, charRPar & !LookAhead[SkipWs & (charDigit | charLPar)]) { new RightDelimiterRule { 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 { 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 { Create(IdTagName, (string tagName) => tagName) } }; // token: tag close var tagEnd = new Token(IdTag, charLt & charSlash & new Token(IdTagName, CharWord.Repeat()) & LookAhead[charGt]) { new RightDelimiterRule { 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( 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 = "42"; string testOutput = "x=42"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] [ExpectedException(typeof(ParseErrorException))] public void TestConstError() { string testInput = "foo"; Parser.Parse(testInput); } [TestMethod] public void TestInfix() { string testInput = "2 - 1"; string testOutput = "x=1"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] [ExpectedException(typeof(ParseErrorException))] public void TestInfixError() { string testInput = "2 - "; Parser.Parse(testInput); } [TestMethod] public void TestPrefix() { string testInput = "-2 + 1"; string testOutput = "x=-1"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] [ExpectedException(typeof(ParseErrorException))] public void TestPrefixError() { string testInput = "- + 1"; Parser.Parse(testInput); } [TestMethod] public void TestPrecedence() { string testInput = "2 + 3 * 4"; string testOutput = "x=14"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] public void TestParentheses() { string testInput = "(2 + 3) * 4"; string testOutput = "x=20"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] [ExpectedException(typeof(ParseErrorException))] public void TestParenthesesLeftError() { string testInput = "2 + 3) * 4"; Parser.Parse(testInput); } [TestMethod] [ExpectedException(typeof(ParseErrorException))] public void TestParenthesesRightError() { string testInput = "(2 + 3 * 4"; Parser.Parse(testInput); } [TestMethod] public void TestParenthesesNested() { string testInput = "(-((2 + 3) * 4) / 5) * 3"; string testOutput = "x=-12"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] public void TestNestedTags() { string testInput = "(-((2 + 3) * 4) / 5) * 3(2 + 3) * 4"; string testOutput = "a:{x=-12,y=20}"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } [TestMethod] [ExpectedException(typeof(ParseErrorException))] public void TestNestedTagsError() { string testInput = "12"; Parser.Parse(testInput); } [TestMethod] public void TestMultiLines() { string testInput = "" + "\r\n" + " 2 + 3 * 4" + "\r\n" + " (2 + 3) * 4" + "\r\n" + ""; string testOutput = "a:{x=14,y=20}"; Debug.Assert(Parser.Parse(testInput).GetValues(IdTag).First() == testOutput); } } }