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..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 @@ -21,6 +21,10 @@ */ package com.github._1c_syntax.bsl.languageserver.cfg; +import com.google.common.collect.Sets; + +import java.util.Set; + public enum PreprocessorConstraints { SERVER, CLIENT, @@ -30,9 +34,28 @@ 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 Set CLIENT_CONSTRAINTS = Sets.immutableEnumSet( + ORDINARY_THICK_CLIENT, + MANAGED_THICK_CLIENT, + MOBILE_CLIENT, + THIN_CLIENT, + WEB_CLIENT); + public static final Set DEFAULT_CONSTRAINTS = Sets.immutableEnumSet( + 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 new file mode 100644 index 00000000000..32d0abedb78 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/bsl/Preprocessor.java @@ -0,0 +1,135 @@ +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; + + 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; + default: + symbol = PreprocessorConstraints.NON_STANDARD; + 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) { + + var subset = calculatePreprocessorConstraints(((UnaryOperationNode) expression).getOperand()); + return getInventorySet(subset); + + } 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 EnumSet.copyOf(PreprocessorConstraints.CLIENT_CONSTRAINTS); + } else { + return EnumSet.of(constraint); + } + } + + private static EnumSet getInventorySet(EnumSet set) { + var resultSet = EnumSet.copyOf(PreprocessorConstraints.DEFAULT_CONSTRAINTS); + 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..0e8638cd1f0 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/PreprocessorExpressionTreeBuildingVisitor.java @@ -0,0 +1,148 @@ +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 final Deque states = new ArrayDeque<>(); + + BslExpression getExpressionTree() { + return resultExpression; + } + + @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) { + + var currentState = new State(); + states.push(currentState); + + super.visitPreproc_logicalExpression(ctx); + + while (!currentState.operators.isEmpty()) { + buildOperation(ctx); + } + + states.remove(); + 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())); + } else { + super.visitPreproc_logicalOperand(ctx); + } + + if (ctx.PREPROC_NOT_KEYWORD() != null) { + pushNot(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) { + var operators = getOperators(); + 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) { + var operators = getOperators(); + if (operators.isEmpty()) { + return; + } + + var operands = getOperands(); + 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); + } + } + + 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 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/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); + } +} 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..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 @@ -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,130 @@ 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 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 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 ИЛИ Клиент)"); + 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); 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