From 8c26fd07551c200d4fe355d1f7a20ed01a8958a0 Mon Sep 17 00:00:00 2001 From: alkoleft Date: Fri, 11 Nov 2022 18:11:51 +0300 Subject: [PATCH 1/5] #2934 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Построитель дерева выражений Вычисление условий препроцессора --- .../cfg/PreprocessorConstraints.java | 11 +- .../utils/bsl/Preprocessor.java | 128 ++++++++++++++++++ .../ExpressionParseTreeRewriter.java | 10 ++ ...rocessorExpressionTreeBuildingVisitor.java | 105 ++++++++++++++ .../PreprocessorSymbolNode.java | 19 +++ 5 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorSymbolNode.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java index a8bb99362e9..05b58c9e221 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java @@ -21,6 +21,8 @@ */ package com.github._1c_syntax.bsl.languageserver.cfg; +import java.util.EnumSet; + public enum PreprocessorConstraints { SERVER, CLIENT, @@ -30,9 +32,16 @@ public enum PreprocessorConstraints { WEB_CLIENT, MOBILE_CLIENT, MOBILE_APP_CLIENT, + MOBILE_STANDALONE_SERVER, MOBILE_APP_SERVER, EXTERNAL_CONNECTION, - NON_STANDARD + NON_STANDARD; + + public static final EnumSet CLIENT_CONSTRAINTS = EnumSet.of(ORDINARY_THICK_CLIENT, + MANAGED_THICK_CLIENT, + MOBILE_CLIENT, + THIN_CLIENT, + WEB_CLIENT); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java new file mode 100644 index 00000000000..b93bf735c22 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java @@ -0,0 +1,128 @@ +package com.github._1c_syntax.bsl.languageserver.utils.bsl; + +import com.github._1c_syntax.bsl.languageserver.cfg.PreprocessorConstraints; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.BinaryOperationNode; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.BslExpression; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.BslOperator; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.PreprocessorSymbolNode; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.UnaryOperationNode; +import com.github._1c_syntax.bsl.parser.BSLParser; +import lombok.experimental.UtilityClass; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.util.EnumSet; + +/** + * Набор методов для работы с препроцессором языка BSL + */ +@UtilityClass +public class Preprocessor { + + /** + * @param ctx контекст синтаксического дерева + * @return ограничение препроцессора + */ + public static PreprocessorConstraints getPreprocessorConstraint(BSLParser.Preproc_symbolContext ctx) { + PreprocessorConstraints symbol = PreprocessorConstraints.NON_STANDARD; + + switch (((TerminalNode) ctx.getChild(0)).getSymbol().getType()) { + case BSLParser.PREPROC_ATSERVER_SYMBOL: + case BSLParser.PREPROC_SERVER_SYMBOL: + symbol = PreprocessorConstraints.SERVER; + break; + case BSLParser.PREPROC_CLIENT_SYMBOL: + case BSLParser.PREPROC_ATCLIENT_SYMBOL: + symbol = PreprocessorConstraints.CLIENT; + break; + case BSLParser.PREPROC_THINCLIENT_SYMBOL: + symbol = PreprocessorConstraints.THIN_CLIENT; + break; + case BSLParser.PREPROC_MOBILECLIENT_SYMBOL: + symbol = PreprocessorConstraints.MOBILE_CLIENT; + break; + case BSLParser.PREPROC_WEBCLIENT_SYMBOL: + symbol = PreprocessorConstraints.WEB_CLIENT; + break; + case BSLParser.PREPROC_EXTERNALCONNECTION_SYMBOL: + symbol = PreprocessorConstraints.EXTERNAL_CONNECTION; + break; + case BSLParser.PREPROC_THICKCLIENTMANAGEDAPPLICATION_SYMBOL: + symbol = PreprocessorConstraints.MANAGED_THICK_CLIENT; + break; + case BSLParser.PREPROC_THICKCLIENTORDINARYAPPLICATION_SYMBOL: + symbol = PreprocessorConstraints.ORDINARY_THICK_CLIENT; + break; + case BSLParser.PREPROC_MOBILE_STANDALONE_SERVER: + symbol = PreprocessorConstraints.MOBILE_STANDALONE_SERVER; + break; + case BSLParser.PREPROC_MOBILEAPPCLIENT_SYMBOL: + symbol = PreprocessorConstraints.MOBILE_APP_CLIENT; + break; + case BSLParser.PREPROC_MOBILEAPPSERVER_SYMBOL: + symbol = PreprocessorConstraints.MOBILE_APP_SERVER; + break; + } + return symbol; + } + + /** + * Вычисляет результат условия препроцессора + * @param expression условие препроцессора + * @return результирующий набор контекстов + */ + public static EnumSet calculatePreprocessorConstraints(BslExpression expression) { + + if (expression instanceof PreprocessorSymbolNode) { + + return getConstraintSet(((PreprocessorSymbolNode) expression).getSymbol()); + + } else if (expression instanceof UnaryOperationNode && ((UnaryOperationNode) expression).getOperator() == BslOperator.NOT) { + + return getInventorySet(calculatePreprocessorConstraints(((UnaryOperationNode) expression).getOperand())); + + } else if (expression instanceof BinaryOperationNode) { + + var operation = (BinaryOperationNode) expression; + + if (operation.getOperator() == BslOperator.AND) { + + return retainSets(calculatePreprocessorConstraints(operation.getLeft()), + calculatePreprocessorConstraints(operation.getRight())); + + } else if (operation.getOperator() == BslOperator.OR) { + + return joinSets(calculatePreprocessorConstraints(operation.getLeft()), + calculatePreprocessorConstraints(operation.getRight())); + + } + } + + throw new IllegalStateException(); + } + + private static EnumSet getConstraintSet(PreprocessorConstraints constraint) { + if (constraint == PreprocessorConstraints.CLIENT) { + return PreprocessorConstraints.CLIENT_CONSTRAINTS; + } else { + return EnumSet.of(constraint); + } + } + + private static EnumSet getInventorySet(EnumSet set) { + var resultSet = EnumSet.allOf(PreprocessorConstraints.class); + resultSet.removeAll(set); + return resultSet; + } + + private static EnumSet joinSets(EnumSet firstSet, EnumSet secondSet) { + var resultSet = firstSet.clone(); + resultSet.addAll(secondSet); + return resultSet; + } + + private static EnumSet retainSets(EnumSet firstSet, EnumSet secondSet) { + var resultSet = firstSet.clone(); + resultSet.retainAll(secondSet); + return resultSet; + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionParseTreeRewriter.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionParseTreeRewriter.java index bf5541799e4..af1d5c8012d 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionParseTreeRewriter.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionParseTreeRewriter.java @@ -41,4 +41,14 @@ public static BslExpression buildExpressionTree(BSLParser.ExpressionContext expr return visitor.getExpressionTree(); } + /** + * Строит дерево выражений для условия препроцессора + * @param expression ast дерево выражения + * @return результирующее выражение в виде дерева вычисления операций + */ + public static BslExpression buildExpressionTree(BSLParser.Preproc_expressionContext expression) { + var visitor = new PreprocessorExpressionTreeBuildingVisitor(); + visitor.visitPreproc_expression(expression); + return visitor.getExpressionTree(); + } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java new file mode 100644 index 00000000000..e1ba4715d6c --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java @@ -0,0 +1,105 @@ +package com.github._1c_syntax.bsl.languageserver.utils.expressiontree; + +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor; +import org.antlr.v4.runtime.tree.ParseTree; + +import java.util.ArrayDeque; +import java.util.Deque; + +class PreprocessorExpressionTreeBuildingVisitor extends BSLParserBaseVisitor { + + private BslExpression resultExpression; + private int recursionLevel = -1; + + private final Deque operands = new ArrayDeque<>(); + private final Deque operators = new ArrayDeque<>(); + + BslExpression getExpressionTree() { + return resultExpression; + } + + @Override + public ParseTree visitPreproc_expression(BSLParser.Preproc_expressionContext ctx) { + return super.visitPreproc_expression(ctx); + } + + @Override + public ParseTree visitPreproc_logicalExpression(BSLParser.Preproc_logicalExpressionContext ctx) { + var nestingCount = operators.size(); + recursionLevel++; + var addToOperands = recursionLevel > 0; + + super.visitPreproc_logicalExpression(ctx); + + while (nestingCount < operators.size()) { + buildOperation(ctx); + } + + if (!addToOperands) { + resultExpression = operands.pop(); + } + + recursionLevel--; + return ctx; + } + + @Override + public ParseTree visitPreproc_logicalOperand(BSLParser.Preproc_logicalOperandContext ctx) { + + if (ctx.preproc_symbol() != null) { + operands.push(new PreprocessorSymbolNode(ctx.preproc_symbol())); + } else { + super.visitPreproc_logicalOperand(ctx); + } + + if (ctx.PREPROC_NOT_KEYWORD() != null) { + operators.push(BslOperator.NOT); + buildOperation(ctx); + } + + return ctx; + } + + @Override + public ParseTree visitPreproc_boolOperation(BSLParser.Preproc_boolOperationContext ctx) { + if (ctx.PREPROC_AND_KEYWORD() != null) { + processOperation(BslOperator.AND, ctx); + } else if (ctx.PREPROC_OR_KEYWORD() != null) { + processOperation(BslOperator.OR, ctx); + } + return ctx; + } + + private void processOperation(BslOperator operator, ParseTree ctx) { + if (operators.isEmpty()) { + operators.push(operator); + return; + } + + var lastSeenOperator = operators.peek(); + if (lastSeenOperator.getPriority() > operator.getPriority()) { + buildOperation(ctx); + } + + operators.push(operator); + } + + private void buildOperation(ParseTree ctx) { + if (operators.isEmpty()) { + return; + } + + var operator = operators.pop(); + if (operator == BslOperator.NOT) { + var operand = operands.pop(); + var operation = UnaryOperationNode.create(operator, operand, ctx); + operands.push(operation); + } else { + var right = operands.pop(); + var left = operands.pop(); + var binaryOp = BinaryOperationNode.create(operator, left, right, ctx); + operands.push(binaryOp); + } + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorSymbolNode.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorSymbolNode.java new file mode 100644 index 00000000000..faef84bbbaf --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorSymbolNode.java @@ -0,0 +1,19 @@ +package com.github._1c_syntax.bsl.languageserver.utils.expressiontree; + +import com.github._1c_syntax.bsl.languageserver.cfg.PreprocessorConstraints; +import com.github._1c_syntax.bsl.languageserver.utils.bsl.Preprocessor; +import com.github._1c_syntax.bsl.parser.BSLParser; +import lombok.Getter; +import lombok.ToString; + +@ToString +public class PreprocessorSymbolNode extends BslExpression { + + @Getter + private final PreprocessorConstraints symbol; + + PreprocessorSymbolNode(BSLParser.Preproc_symbolContext ctx) { + super(ExpressionNodeType.LITERAL); + symbol = Preprocessor.getPreprocessorConstraint(ctx); + } +} From 63348ed1b827376602786b7d283cf94c302acc45 Mon Sep 17 00:00:00 2001 From: alkoleft Date: Fri, 11 Nov 2022 23:52:35 +0300 Subject: [PATCH 2/5] #2934 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Тесты Исправлена ошибка обработки выражения в скобках --- ...rocessorExpressionTreeBuildingVisitor.java | 52 ++++++-- .../ExpressionParseTreeRewriterTest.java | 125 ++++++++++++++++++ 2 files changed, 165 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java index e1ba4715d6c..b65c274d414 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java @@ -10,10 +10,8 @@ class PreprocessorExpressionTreeBuildingVisitor extends BSLParserBaseVisitor { private BslExpression resultExpression; - private int recursionLevel = -1; - private final Deque operands = new ArrayDeque<>(); - private final Deque operators = new ArrayDeque<>(); + private final Deque states = new ArrayDeque<>(); BslExpression getExpressionTree() { return resultExpression; @@ -21,31 +19,37 @@ BslExpression getExpressionTree() { @Override public ParseTree visitPreproc_expression(BSLParser.Preproc_expressionContext ctx) { - return super.visitPreproc_expression(ctx); + super.visitPreproc_expression(ctx); + + return ctx; } @Override public ParseTree visitPreproc_logicalExpression(BSLParser.Preproc_logicalExpressionContext ctx) { - var nestingCount = operators.size(); - recursionLevel++; - var addToOperands = recursionLevel > 0; + + boolean isRoot = states.isEmpty(); + var currentState = new State(); + states.push(currentState); super.visitPreproc_logicalExpression(ctx); - while (nestingCount < operators.size()) { + while (!currentState.operators.isEmpty()) { buildOperation(ctx); } - if (!addToOperands) { - resultExpression = operands.pop(); - } + states.remove(); - recursionLevel--; + if (isRoot) { + resultExpression = currentState.operands.pop(); + } else { + getOperands().push(currentState.operands.pop()); + } return ctx; } @Override public ParseTree visitPreproc_logicalOperand(BSLParser.Preproc_logicalOperandContext ctx) { + var operands = getOperands(); if (ctx.preproc_symbol() != null) { operands.push(new PreprocessorSymbolNode(ctx.preproc_symbol())); @@ -54,6 +58,7 @@ public ParseTree visitPreproc_logicalOperand(BSLParser.Preproc_logicalOperandCon } if (ctx.PREPROC_NOT_KEYWORD() != null) { + var operators = getOperators(); operators.push(BslOperator.NOT); buildOperation(ctx); } @@ -72,6 +77,7 @@ public ParseTree visitPreproc_boolOperation(BSLParser.Preproc_boolOperationConte } private void processOperation(BslOperator operator, ParseTree ctx) { + var operators = getOperators(); if (operators.isEmpty()) { operators.push(operator); return; @@ -86,10 +92,12 @@ private void processOperation(BslOperator operator, ParseTree ctx) { } private void buildOperation(ParseTree ctx) { + var operators = getOperators(); if (operators.isEmpty()) { return; } + var operands = getOperands(); var operator = operators.pop(); if (operator == BslOperator.NOT) { var operand = operands.pop(); @@ -102,4 +110,24 @@ private void buildOperation(ParseTree ctx) { operands.push(binaryOp); } } + + private Deque getOperands() { + if (states.isEmpty()) { + throw new IllegalStateException(); + } + return states.peek().operands; + } + + private Deque getOperators() { + if (states.isEmpty()) { + throw new IllegalStateException(); + } + return states.peek().operators; + } + + private static class State { + private final Deque operands = new ArrayDeque<>(); + private final Deque operators = new ArrayDeque<>(); + + } } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java index 3893037cf21..7cb99b8a368 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java @@ -21,6 +21,7 @@ */ package com.github._1c_syntax.bsl.languageserver.utils; +import com.github._1c_syntax.bsl.languageserver.cfg.PreprocessorConstraints; import com.github._1c_syntax.bsl.languageserver.util.TestUtils; import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.BinaryOperationNode; import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.BslExpression; @@ -30,12 +31,15 @@ import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.ExpressionNodeType; import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.ExpressionParseTreeRewriter; import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.MethodCallNode; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.PreprocessorSymbolNode; import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.SkippedCallArgumentNode; import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.UnaryOperationNode; import com.github._1c_syntax.bsl.parser.BSLParser; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import java.util.Map; + import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; @@ -317,6 +321,127 @@ void realLifeHardExpression() { assertThat(binary.getRight().cast().getOperator()).isEqualTo(BslOperator.EQUAL); } + @Test + void preprocessorUno() { + var variants = Map.ofEntries( + Map.entry("Клиент", PreprocessorConstraints.CLIENT), + Map.entry("Client", PreprocessorConstraints.CLIENT), + Map.entry("НаКлиенте", PreprocessorConstraints.CLIENT), + Map.entry("AtClient", PreprocessorConstraints.CLIENT), + Map.entry("НаСервере", PreprocessorConstraints.SERVER), + Map.entry("AtServer", PreprocessorConstraints.SERVER), + Map.entry("Сервер", PreprocessorConstraints.SERVER), + Map.entry("Server", PreprocessorConstraints.SERVER), + Map.entry("ТонкийКлиент", PreprocessorConstraints.THIN_CLIENT), + Map.entry("ThinClient", PreprocessorConstraints.THIN_CLIENT), + Map.entry("ВебКлиент", PreprocessorConstraints.WEB_CLIENT), + Map.entry("WebClient", PreprocessorConstraints.WEB_CLIENT), + Map.entry("МобильныйАвтономныйСервер", PreprocessorConstraints.MOBILE_STANDALONE_SERVER), + Map.entry("MobileStandaloneServer", PreprocessorConstraints.MOBILE_STANDALONE_SERVER), + Map.entry("МобильноеПриложениеКлиент", PreprocessorConstraints.MOBILE_APP_CLIENT), + Map.entry("MobileAppClient", PreprocessorConstraints.MOBILE_APP_CLIENT), + Map.entry("МобильноеПриложениеСервер", PreprocessorConstraints.MOBILE_APP_SERVER), + Map.entry("MobileAppServer", PreprocessorConstraints.MOBILE_APP_SERVER), + Map.entry("МобильныйКлиент", PreprocessorConstraints.MOBILE_CLIENT), + Map.entry("MobileClient", PreprocessorConstraints.MOBILE_CLIENT), + Map.entry("ТолстыйКлиентОбычноеПриложение", PreprocessorConstraints.ORDINARY_THICK_CLIENT), + Map.entry("ThickClientOrdinaryApplication", PreprocessorConstraints.ORDINARY_THICK_CLIENT), + Map.entry("ТолстыйКлиентУправляемоеПриложение", PreprocessorConstraints.MANAGED_THICK_CLIENT), + Map.entry("ThickClientManagedApplication", PreprocessorConstraints.MANAGED_THICK_CLIENT), + Map.entry("ВнешнееСоединение", PreprocessorConstraints.EXTERNAL_CONNECTION), + Map.entry("ExternalConnection", PreprocessorConstraints.EXTERNAL_CONNECTION)); + + for (var variant : variants.entrySet()) { + var expression = getPreprocessorExpressionTree(variant.getKey()); + assertThat(expression).isInstanceOf(PreprocessorSymbolNode.class); + assertThat(((PreprocessorSymbolNode) expression).getSymbol()).isEqualTo(variant.getValue()); + } + } + + @Test + void preprocessorNot() { + var variants = Map.of( + "Not Клиент", PreprocessorConstraints.CLIENT, + "Не Server", PreprocessorConstraints.SERVER + ); + + for (var variant : variants.entrySet()) { + var expression = getPreprocessorExpressionTree(variant.getKey()); + assertThat(expression).isInstanceOf(UnaryOperationNode.class); + var operation = (UnaryOperationNode) expression; + assertThat(operation.getOperator()).isEqualTo(BslOperator.NOT); + assertThat(operation.getOperand()).isInstanceOf(PreprocessorSymbolNode.class); + assertThat(((PreprocessorSymbolNode) operation.getOperand()).getSymbol()).isEqualTo(variant.getValue()); + } + } + + @Test + void preprocessorAND() { + var expression = getPreprocessorExpressionTree("Сервер И Клиент"); + assertThat(expression).isInstanceOf(BinaryOperationNode.class); + var operation = (BinaryOperationNode) expression; + assertThat(operation.getOperator()).isEqualTo(BslOperator.AND); + assertThat(operation.getLeft()) + .isInstanceOf(PreprocessorSymbolNode.class) + .extracting("symbol").isEqualTo(PreprocessorConstraints.SERVER) + ; + assertThat(operation.getRight()) + .isInstanceOf(PreprocessorSymbolNode.class) + .extracting("symbol").isEqualTo(PreprocessorConstraints.CLIENT) + ; + expression = getPreprocessorExpressionTree("НЕ Сервер И Клиент"); + assertThat(expression) + .extracting("left").isInstanceOf(UnaryOperationNode.class) + .extracting("operand") + .isInstanceOf(PreprocessorSymbolNode.class) + .extracting("symbol").isEqualTo(PreprocessorConstraints.SERVER) + ; + expression = getPreprocessorExpressionTree("Клиент AND Server AND MobileAppClient"); + operation = (BinaryOperationNode) expression; + assertThat(operation.getLeft()).isInstanceOf(PreprocessorSymbolNode.class) + .extracting("symbol").isEqualTo(PreprocessorConstraints.CLIENT); + assertThat(operation.getRight()).isInstanceOf(BinaryOperationNode.class); + } + + @Test + void preprocessorOR() { + var expression = getPreprocessorExpressionTree("Сервер ИЛИ Клиент"); + assertThat(expression).isInstanceOf(BinaryOperationNode.class); + var operation = (BinaryOperationNode) expression; + assertThat(operation.getOperator()).isEqualTo(BslOperator.OR); + assertThat(operation.getLeft()) + .isInstanceOf(PreprocessorSymbolNode.class) + .extracting("symbol").isEqualTo(PreprocessorConstraints.SERVER) + ; + expression = getPreprocessorExpressionTree("Клиент OR Server OR MobileAppClient"); + operation = (BinaryOperationNode) expression; + assertThat(operation.getLeft()).isInstanceOf(PreprocessorSymbolNode.class) + .extracting("symbol").isEqualTo(PreprocessorConstraints.CLIENT); + assertThat(operation.getRight()).isInstanceOf(BinaryOperationNode.class); + } + + @Test + void preprocessorComplex() { + var expression = getPreprocessorExpressionTree("Client AND Not MobileClient OR Server И (ExternalConnection ИЛИ Клиент)"); + var operation = (BinaryOperationNode) expression; + assertThat(operation.getOperator()).isEqualTo(BslOperator.OR); + assertThat(operation.getLeft()) + .extracting("left.symbol", "operator", "right.operator", "right.operand.symbol") + .containsExactly(PreprocessorConstraints.CLIENT, BslOperator.AND, BslOperator.NOT, PreprocessorConstraints.MOBILE_CLIENT) + ; + assertThat(operation.getRight()) + .extracting("left.symbol", "operator", "right.left.symbol", "right.operator", "right.right.symbol") + .containsExactly(PreprocessorConstraints.SERVER, BslOperator.AND, PreprocessorConstraints.EXTERNAL_CONNECTION, BslOperator.OR, PreprocessorConstraints.CLIENT) + ; + } + + BslExpression getPreprocessorExpressionTree(String code) { + var preprocessorPredicate = String.format("#Если %s Тогда\n#КонецЕсли", code); + var dContext = TestUtils.getDocumentContext(preprocessorPredicate); + var expression = dContext.getAst().preprocessor(0).preproc_if().preproc_expression(); + return ExpressionParseTreeRewriter.buildExpressionTree(expression); + } + BslExpression getExpressionTree(String code) { var expression = parse(code); return ExpressionParseTreeRewriter.buildExpressionTree(expression); From 89a6ab5110c02a8ec74025a98a4f6407e1d000eb Mon Sep 17 00:00:00 2001 From: alkoleft Date: Sat, 12 Nov 2022 00:58:31 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit для `НЕ (Сервер ИЛИ Клиент)` --- ...rocessorExpressionTreeBuildingVisitor.java | 33 ++++++++++++----- .../ExpressionParseTreeRewriterTest.java | 37 ++++++++++--------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java index b65c274d414..0e8638cd1f0 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java @@ -19,15 +19,30 @@ BslExpression getExpressionTree() { @Override public ParseTree visitPreproc_expression(BSLParser.Preproc_expressionContext ctx) { + + boolean isRoot = states.isEmpty(); + var currentState = new State(); + states.push(currentState); + super.visitPreproc_expression(ctx); + if (ctx.PREPROC_NOT_KEYWORD() != null) { + pushNot(ctx); + } + states.remove(); + + if (isRoot) { + resultExpression = currentState.operands.pop(); + } else { + getOperands().push(currentState.operands.pop()); + } + return ctx; } @Override public ParseTree visitPreproc_logicalExpression(BSLParser.Preproc_logicalExpressionContext ctx) { - boolean isRoot = states.isEmpty(); var currentState = new State(); states.push(currentState); @@ -38,12 +53,8 @@ public ParseTree visitPreproc_logicalExpression(BSLParser.Preproc_logicalExpress } states.remove(); + getOperands().push(currentState.operands.pop()); - if (isRoot) { - resultExpression = currentState.operands.pop(); - } else { - getOperands().push(currentState.operands.pop()); - } return ctx; } @@ -58,9 +69,7 @@ public ParseTree visitPreproc_logicalOperand(BSLParser.Preproc_logicalOperandCon } if (ctx.PREPROC_NOT_KEYWORD() != null) { - var operators = getOperators(); - operators.push(BslOperator.NOT); - buildOperation(ctx); + pushNot(ctx); } return ctx; @@ -125,6 +134,12 @@ private Deque getOperators() { return states.peek().operators; } + private void pushNot(ParseTree ctx) { + var operators = getOperators(); + operators.push(BslOperator.NOT); + buildOperation(ctx); + } + private static class State { private final Deque operands = new ArrayDeque<>(); private final Deque operators = new ArrayDeque<>(); diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java index 7cb99b8a368..988773f9da8 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ExpressionParseTreeRewriterTest.java @@ -358,23 +358,6 @@ void preprocessorUno() { } } - @Test - void preprocessorNot() { - var variants = Map.of( - "Not Клиент", PreprocessorConstraints.CLIENT, - "Не Server", PreprocessorConstraints.SERVER - ); - - for (var variant : variants.entrySet()) { - var expression = getPreprocessorExpressionTree(variant.getKey()); - assertThat(expression).isInstanceOf(UnaryOperationNode.class); - var operation = (UnaryOperationNode) expression; - assertThat(operation.getOperator()).isEqualTo(BslOperator.NOT); - assertThat(operation.getOperand()).isInstanceOf(PreprocessorSymbolNode.class); - assertThat(((PreprocessorSymbolNode) operation.getOperand()).getSymbol()).isEqualTo(variant.getValue()); - } - } - @Test void preprocessorAND() { var expression = getPreprocessorExpressionTree("Сервер И Клиент"); @@ -420,6 +403,26 @@ void preprocessorOR() { assertThat(operation.getRight()).isInstanceOf(BinaryOperationNode.class); } + @Test + void preprocessorNot() { + var expression = getPreprocessorExpressionTree("Not Клиент"); + assertThat(expression).isInstanceOf(UnaryOperationNode.class) + .extracting("operator", "operand.symbol") + .containsExactly(BslOperator.NOT, PreprocessorConstraints.CLIENT); + + expression = getPreprocessorExpressionTree("Не AtServer"); + assertThat(expression) + .extracting("operator", "operand.symbol") + .containsExactly(BslOperator.NOT, PreprocessorConstraints.SERVER); + expression = getPreprocessorExpressionTree("НЕ (Сервер ИЛИ Клиент)"); + assertThat(expression) + .isInstanceOf(UnaryOperationNode.class) + .extracting("operand") + .isInstanceOf(BinaryOperationNode.class) + .extracting("left.symbol", "operator", "right.symbol") + .containsExactly(PreprocessorConstraints.SERVER, BslOperator.OR, PreprocessorConstraints.CLIENT); + } + @Test void preprocessorComplex() { var expression = getPreprocessorExpressionTree("Client AND Not MobileClient OR Server И (ExternalConnection ИЛИ Клиент)"); From 10f97f1c797615ab0c07aa8f166794ebeeb873ef Mon Sep 17 00:00:00 2001 From: alkoleft Date: Sat, 12 Nov 2022 00:59:21 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=8B=D1=87=D0=B8=D1=81=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=83=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=BE?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cfg/PreprocessorConstraints.java | 20 +++++- .../utils/bsl/Preprocessor.java | 14 ++-- .../utils/bsl/PreprocessorTest.java | 70 +++++++++++++++++++ 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/PreprocessorTest.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java index 05b58c9e221..e8bbe0c4acd 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java @@ -21,7 +21,10 @@ */ package com.github._1c_syntax.bsl.languageserver.cfg; +import com.google.common.collect.Sets; + import java.util.EnumSet; +import java.util.Set; public enum PreprocessorConstraints { SERVER, @@ -38,10 +41,23 @@ public enum PreprocessorConstraints { NON_STANDARD; - public static final EnumSet CLIENT_CONSTRAINTS = EnumSet.of(ORDINARY_THICK_CLIENT, + public static final Set CLIENT_CONSTRAINTS = Sets.immutableEnumSet(EnumSet.of( + ORDINARY_THICK_CLIENT, MANAGED_THICK_CLIENT, MOBILE_CLIENT, THIN_CLIENT, - WEB_CLIENT); + WEB_CLIENT)); + + public static final Set DEFAULT_CONSTRAINTS = Sets.immutableEnumSet(EnumSet.of( + SERVER, + THIN_CLIENT, + MANAGED_THICK_CLIENT, + ORDINARY_THICK_CLIENT, + WEB_CLIENT, + MOBILE_CLIENT, + MOBILE_APP_CLIENT, + MOBILE_STANDALONE_SERVER, + MOBILE_APP_SERVER, + EXTERNAL_CONNECTION)); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java index b93bf735c22..2e45bbb0963 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java @@ -67,6 +67,7 @@ public static PreprocessorConstraints getPreprocessorConstraint(BSLParser.Prepro /** * Вычисляет результат условия препроцессора + * * @param expression условие препроцессора * @return результирующий набор контекстов */ @@ -78,7 +79,8 @@ public static EnumSet calculatePreprocessorConstraints( } else if (expression instanceof UnaryOperationNode && ((UnaryOperationNode) expression).getOperator() == BslOperator.NOT) { - return getInventorySet(calculatePreprocessorConstraints(((UnaryOperationNode) expression).getOperand())); + var subset = calculatePreprocessorConstraints(((UnaryOperationNode) expression).getOperand()); + return getInventorySet(subset); } else if (expression instanceof BinaryOperationNode) { @@ -86,12 +88,14 @@ public static EnumSet calculatePreprocessorConstraints( if (operation.getOperator() == BslOperator.AND) { - return retainSets(calculatePreprocessorConstraints(operation.getLeft()), + return retainSets( + calculatePreprocessorConstraints(operation.getLeft()), calculatePreprocessorConstraints(operation.getRight())); } else if (operation.getOperator() == BslOperator.OR) { - return joinSets(calculatePreprocessorConstraints(operation.getLeft()), + return joinSets( + calculatePreprocessorConstraints(operation.getLeft()), calculatePreprocessorConstraints(operation.getRight())); } @@ -102,14 +106,14 @@ public static EnumSet calculatePreprocessorConstraints( private static EnumSet getConstraintSet(PreprocessorConstraints constraint) { if (constraint == PreprocessorConstraints.CLIENT) { - return PreprocessorConstraints.CLIENT_CONSTRAINTS; + return EnumSet.copyOf(PreprocessorConstraints.CLIENT_CONSTRAINTS); } else { return EnumSet.of(constraint); } } private static EnumSet getInventorySet(EnumSet set) { - var resultSet = EnumSet.allOf(PreprocessorConstraints.class); + var resultSet = EnumSet.copyOf(PreprocessorConstraints.DEFAULT_CONSTRAINTS); resultSet.removeAll(set); return resultSet; } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/PreprocessorTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/PreprocessorTest.java new file mode 100644 index 00000000000..dbe81be42d9 --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/PreprocessorTest.java @@ -0,0 +1,70 @@ +package com.github._1c_syntax.bsl.languageserver.utils.bsl; + +import com.github._1c_syntax.bsl.languageserver.cfg.PreprocessorConstraints; +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.BslExpression; +import com.github._1c_syntax.bsl.languageserver.utils.expressiontree.ExpressionParseTreeRewriter; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +class PreprocessorTest { + + @Test + void calculatePreprocessorConstraints() { + var variants = Map.ofEntries( + Map.entry("ТонкийКлиент", EnumSet.of(PreprocessorConstraints.THIN_CLIENT)), + Map.entry("Клиент", EnumSet.of( + PreprocessorConstraints.ORDINARY_THICK_CLIENT, + PreprocessorConstraints.MANAGED_THICK_CLIENT, + PreprocessorConstraints.MOBILE_CLIENT, + PreprocessorConstraints.THIN_CLIENT, + PreprocessorConstraints.WEB_CLIENT)), + Map.entry("ТонкийКлиент ИЛИ ВебКлиент", EnumSet.of( + PreprocessorConstraints.THIN_CLIENT, + PreprocessorConstraints.WEB_CLIENT)), + Map.entry("Клиент И (ТонкийКлиент ИЛИ ВебКлиент)", EnumSet.of( + PreprocessorConstraints.THIN_CLIENT, + PreprocessorConstraints.WEB_CLIENT)), + Map.entry("НЕ Сервер", without(PreprocessorConstraints.DEFAULT_CONSTRAINTS, PreprocessorConstraints.SERVER)), + Map.entry("НЕ (Сервер ИЛИ ТонкийКлиент)", without(PreprocessorConstraints.DEFAULT_CONSTRAINTS, PreprocessorConstraints.SERVER, PreprocessorConstraints.THIN_CLIENT)), + Map.entry("НЕ (Сервер ИЛИ Клиент)", EnumSet.of( + PreprocessorConstraints.MOBILE_APP_CLIENT, + PreprocessorConstraints.MOBILE_STANDALONE_SERVER, + PreprocessorConstraints.MOBILE_APP_SERVER, + PreprocessorConstraints.EXTERNAL_CONNECTION)) + ); + + for (var variant : variants.entrySet()) { + var expression = getPreprocessorExpressionTree(variant.getKey()); + var result = Preprocessor.calculatePreprocessorConstraints(expression); + assertThat(result).describedAs("Условие прероцессора: %s", variant.getKey()).isEqualTo(variant.getValue()); + } + } + + BslExpression getPreprocessorExpressionTree(String code) { + var preprocessorPredicate = String.format("#Если %s Тогда\n#КонецЕсли", code); + var dContext = TestUtils.getDocumentContext(preprocessorPredicate); + var expression = dContext.getAst().preprocessor(0).preproc_if().preproc_expression(); + return ExpressionParseTreeRewriter.buildExpressionTree(expression); + } + + @SafeVarargs + final > EnumSet without(Set set, T... value) { + EnumSet result; + if(set instanceof EnumSet){ + result = ((EnumSet)set).clone(); + }else { + result = EnumSet.copyOf(set); + } + Arrays.asList(value).forEach(result::remove); + return result; + } +} \ No newline at end of file From e137a0cf5e56facf17dc1d1e0542431306665a92 Mon Sep 17 00:00:00 2001 From: alkoleft Date: Sat, 12 Nov 2022 21:47:58 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../languageserver/cfg/PreprocessorConstraints.java | 10 ++++------ .../bsl/languageserver/utils/bsl/Preprocessor.java | 5 ++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java index e8bbe0c4acd..46276d4d300 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/PreprocessorConstraints.java @@ -23,7 +23,6 @@ import com.google.common.collect.Sets; -import java.util.EnumSet; import java.util.Set; public enum PreprocessorConstraints { @@ -41,14 +40,14 @@ public enum PreprocessorConstraints { NON_STANDARD; - public static final Set CLIENT_CONSTRAINTS = Sets.immutableEnumSet(EnumSet.of( + public static final Set CLIENT_CONSTRAINTS = Sets.immutableEnumSet( ORDINARY_THICK_CLIENT, MANAGED_THICK_CLIENT, MOBILE_CLIENT, THIN_CLIENT, - WEB_CLIENT)); + WEB_CLIENT); - public static final Set DEFAULT_CONSTRAINTS = Sets.immutableEnumSet(EnumSet.of( + public static final Set DEFAULT_CONSTRAINTS = Sets.immutableEnumSet( SERVER, THIN_CLIENT, MANAGED_THICK_CLIENT, @@ -58,6 +57,5 @@ public enum PreprocessorConstraints { MOBILE_APP_CLIENT, MOBILE_STANDALONE_SERVER, MOBILE_APP_SERVER, - EXTERNAL_CONNECTION)); - + EXTERNAL_CONNECTION); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java index 2e45bbb0963..32d0abedb78 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java @@ -23,7 +23,7 @@ public class Preprocessor { * @return ограничение препроцессора */ public static PreprocessorConstraints getPreprocessorConstraint(BSLParser.Preproc_symbolContext ctx) { - PreprocessorConstraints symbol = PreprocessorConstraints.NON_STANDARD; + PreprocessorConstraints symbol; switch (((TerminalNode) ctx.getChild(0)).getSymbol().getType()) { case BSLParser.PREPROC_ATSERVER_SYMBOL: @@ -61,6 +61,9 @@ public static PreprocessorConstraints getPreprocessorConstraint(BSLParser.Prepro case BSLParser.PREPROC_MOBILEAPPSERVER_SYMBOL: symbol = PreprocessorConstraints.MOBILE_APP_SERVER; break; + default: + symbol = PreprocessorConstraints.NON_STANDARD; + break; } return symbol; }