From 2209ad2ec5d394ddb1fae7ef1608b4527fcf8252 Mon Sep 17 00:00:00 2001 From: Markus Amshove Date: Wed, 30 Aug 2023 08:29:51 +0200 Subject: [PATCH 1/3] Typecheck target and operands of COMPRESS --- .../amshove/natparse/parsing/TypeChecker.java | 36 +++++++++++++++++++ .../parsing/typing/assignAndMathStatements | 2 +- .../natparse/parsing/typing/compressTests | 21 +++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/compressTests diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypeChecker.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypeChecker.java index e27ef0c7b..d696120cf 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypeChecker.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypeChecker.java @@ -81,6 +81,37 @@ private void checkStatement(IStatementNode statement) if (statement instanceof IDecideOnNode decideOn) { checkDecideOnBranches(decideOn); + return; + } + + if (statement instanceof ICompressStatementNode compress) + { + checkCompress(compress); + } + } + + private void checkCompress(ICompressStatementNode compress) + { + for (var operand : compress.operands()) + { + var operandType = inferDataType(operand); + if (operandType.format() == DataFormat.LOGIC || operandType.format() == DataFormat.CONTROL) + { + report(ParserErrors.typeMismatch("COMPRESS operand can't be of type %s".formatted(operandType.format().identifier()), operand)); + } + } + + var targetType = inferDataType(compress.intoTarget()); + if (targetType.format() != DataFormat.ALPHANUMERIC + && targetType.format() != DataFormat.BINARY + && targetType.format() != DataFormat.UNICODE) + { + report( + ParserErrors.typeMismatch( + "COMPRESS target needs to have type A, B or U but got %s".formatted(targetType.toShortString()), + compress.intoTarget() + ) + ); } } @@ -647,6 +678,11 @@ private IDataType inferDataType(IOperandNode operand) return BuiltInFunctionTable.getDefinition(sysVar.systemVariable()).type(); } + if (operand instanceof ISubstringOperandNode substr) + { + return inferDataType(substr.operand()); + } + return new DataType(DataFormat.NONE, IDataType.ONE_GIGABYTE); // couldn't infer, don't raise something yet } diff --git a/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/assignAndMathStatements b/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/assignAndMathStatements index 147007e95..2c4503c58 100644 --- a/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/assignAndMathStatements +++ b/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/assignAndMathStatements @@ -18,7 +18,7 @@ END-DEFINE SUBSTRING(#MUTABLE, 1, 5) := 'hello' /* okay, is mutable COMPRESS 'Hi' INTO SUBSTRING(#C-CONST, 1, 1) /* !{D:ERROR:NPP039} -COMPRESS 'Hi' INTO 5 /* !{D:ERROR:NPP039} +COMPRESS 'Hi' INTO 'Literal' /* !{D:ERROR:NPP039} #MUTABLE := 'Hello' /* Okay, #MUTABLE is not const #C-CONST := 'CD' /* !{D:ERROR:NPP039} this variable is const, this is not allowed diff --git a/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/compressTests b/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/compressTests new file mode 100644 index 000000000..b7c7af151 --- /dev/null +++ b/libs/natparse/src/test/resources/org/amshove/natparse/parsing/typing/compressTests @@ -0,0 +1,21 @@ +DEFINE DATA +LOCAL +1 #ALPHA (A) DYNAMIC +1 #BIN (B) DYNAMIC +1 #UNI (U) DYNAMIC +1 #NUM (N12) +1 #CTRL (C) +1 #LOGIC (L) +END-DEFINE + +COMPRESS #NUM #ALPHA INTO #ALPHA +COMPRESS #NUM #BIN INTO #BIN +COMPRESS #NUM #UNI INTO #UNI + +COMPRESS #ALPHA INTO #NUM /* !{D:ERROR:NPP037} target needs to be A, U or B + +COMPRESS #CTRL INTO #ALPHA /* !{D:ERROR:NPP037} (C) can't be used as operand +COMPRESS #LOGIC INTO #ALPHA /* !{D:ERROR:NPP037} (L) can't be used as operand + + +END From 93dde47fc186a3182924d53330fb3fb776b89437 Mon Sep 17 00:00:00 2001 From: Markus Amshove Date: Wed, 30 Aug 2023 09:25:56 +0200 Subject: [PATCH 2/3] Add function as typed variable to define data --- .../natparse/parsing/DefineDataNode.java | 5 +++++ .../amshove/natparse/parsing/NaturalParser.java | 17 ++++++++++++++++- .../natparse/parsing/TypedVariableNode.java | 1 + .../amshove/natparse/parsing/VariableType.java | 11 +++++++++++ .../natparse/parsing/NaturalParserShould.java | 16 ++++++++++++++++ .../Natural-Libraries/TEST/FUNCSET.NS7 | 2 +- 6 files changed, 50 insertions(+), 2 deletions(-) diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/DefineDataNode.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/DefineDataNode.java index c06e2a4ce..7250b52b9 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/DefineDataNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/DefineDataNode.java @@ -149,6 +149,11 @@ protected void nodeAdded(BaseSyntaxNode node) { addAllVariablesFromScope((IScopeNode) node); } + + if (node instanceof IVariableNode variable) + { + variables.add(variable); + } } private void addAllVariablesFromUsing(IUsingNode usingNode) diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java index 9f0a2e3a3..b7a33329a 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java @@ -54,6 +54,7 @@ private NaturalModule parseModule(NaturalFile file, IModuleProvider moduleProvid return naturalModule; } + VariableNode functionReturnVariable = null; if (file.getFiletype() == NaturalFileType.FUNCTION) // skip over DEFINE FUNCTION { // TODO: Implement proper when implementing different NaturalModules @@ -63,8 +64,15 @@ private NaturalModule parseModule(NaturalFile file, IModuleProvider moduleProvid { break; } - if (tokens.peek().kind() == SyntaxKind.RETURNS) + + if (tokens.peek(1).kind() == SyntaxKind.RETURNS) { + var functionName = tokens.advance(); + functionReturnVariable = new VariableNode(); + functionReturnVariable.setLevel(1); + functionReturnVariable.setScope(VariableScope.LOCAL); + functionReturnVariable.setDeclaration(new TokenNode(functionName)); + tokens.advance(); // RETURNS if (tokens.peek().kind() == SyntaxKind.LPAREN) { @@ -82,6 +90,9 @@ private NaturalModule parseModule(NaturalFile file, IModuleProvider moduleProvid type = DataType.ofDynamicLength(type.format()); } naturalModule.setReturnType(type); + var typedReturnVariable = new TypedVariableNode(functionReturnVariable); + typedReturnVariable.setType(new VariableType(type)); + functionReturnVariable = typedReturnVariable; } advanceToDefineData(tokens); break; @@ -97,6 +108,10 @@ private NaturalModule parseModule(NaturalFile file, IModuleProvider moduleProvid if (advanceToDefineData(tokens)) { topLevelNodes.add(parseDefineData(tokens, moduleProvider, naturalModule)); + if (file.getFiletype() == NaturalFileType.FUNCTION && naturalModule.defineData() != null && functionReturnVariable != null) + { + ((DefineDataNode) naturalModule.defineData()).addNode(functionReturnVariable); + } } if (file.getFiletype().canHaveBody()) diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypedVariableNode.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypedVariableNode.java index 854876fdf..4fd9b666f 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypedVariableNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/TypedVariableNode.java @@ -13,6 +13,7 @@ public TypedVariableNode(VariableNode variable) { setLevel(variable.level()); setDeclaration(variable.identifierNode()); + setScope(variable.scope()); for (var dimension : variable.dimensions()) { addDimension((ArrayDimension) dimension); diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/VariableType.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/VariableType.java index 09f9667af..d7d26def4 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/VariableType.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/VariableType.java @@ -1,6 +1,7 @@ package org.amshove.natparse.parsing; import org.amshove.natparse.natural.DataFormat; +import org.amshove.natparse.natural.IDataType; import org.amshove.natparse.natural.IOperandNode; import org.amshove.natparse.natural.IVariableType; @@ -13,6 +14,16 @@ class VariableType implements IVariableType private IOperandNode initialValue; private boolean isConstant = false; + VariableType() + {} + + VariableType(IDataType other) + { + format = other.format(); + length = other.length(); + hasDynamicLength = other.hasDynamicLength(); + } + @Override public DataFormat format() { diff --git a/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java b/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java index 3a66d6db9..08010e6c7 100644 --- a/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java +++ b/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java @@ -2,6 +2,7 @@ import org.amshove.natparse.natural.DataFormat; import org.amshove.natparse.natural.IFunction; +import org.amshove.natparse.natural.ITypedVariableNode; import org.amshove.natparse.natural.project.NaturalProject; import org.amshove.testhelpers.ProjectName; import org.junit.jupiter.api.Test; @@ -68,6 +69,21 @@ void parseTheReturnTypesOfFunctionsWithFixedLength(@ProjectName("naturalParserTe assertThat(function.returnType().length()).isEqualTo(12.7); } + @Test + void addTheFunctionAsVariableToItsDefineData(@ProjectName("naturalParserTests") NaturalProject project) + { + var module = parse(project.findModule("TEST", "FUNCSET")); + assertThat(module).isInstanceOf(IFunction.class); + var function = (IFunction) module; + assertThat(function.returnType().format()).isEqualTo(DataFormat.NUMERIC); + assertThat(function.returnType().length()).isEqualTo(12.7); + var variable = (ITypedVariableNode) function.defineData().findVariable("FUNCSET"); + assertThat(variable).as("Function name as variable not found").isNotNull(); + assertThat(variable.type().format()).isEqualTo(DataFormat.NUMERIC); + assertThat(variable.type().length()).isEqualTo(12.7); + + } + @Test void reportADiagnosticsForUnreferencedVariablesInFunctions(@ProjectName("variablereferencetests") NaturalProject project) { diff --git a/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNCSET.NS7 b/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNCSET.NS7 index 967cf69d1..c597bb835 100644 --- a/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNCSET.NS7 +++ b/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNCSET.NS7 @@ -2,7 +2,7 @@ * :Mode S * :CP * Date: Wed, 30 Aug 2023 13:27:16 +0200 Subject: [PATCH 3/3] Rudimentary parse function return dimensions --- .../natparse/parsing/NaturalParser.java | 24 ++++++++++++++++++- .../natparse/parsing/NaturalParserShould.java | 22 +++++++++++++++++ .../Natural-Libraries/TEST/FUNC1DIM.NS7 | 17 +++++++++++++ .../Natural-Libraries/TEST/FUNC2.NS7 | 4 ++-- .../Natural-Libraries/TEST/FUNC2DIM.NS7 | 17 +++++++++++++ .../Natural-Libraries/TEST/FUNCDYN.NS7 | 4 ++-- .../Natural-Libraries/TEST/FUNCSET.NS7 | 2 +- 7 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNC1DIM.NS7 create mode 100644 libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNC2DIM.NS7 diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java index b7a33329a..5397da70a 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/NaturalParser.java @@ -84,13 +84,35 @@ private NaturalModule parseModule(NaturalFile file, IModuleProvider moduleProvid typeTokenSource += tokens.advance().source(); // next number } var type = DataType.fromString(typeTokenSource); + var typedReturnVariable = new TypedVariableNode(functionReturnVariable); + + if (typeTokenSource.contains("/") || tokens.peek().kind() == SyntaxKind.SLASH) + { + var firstDimension = new ArrayDimension(); + // Parsing array dimensions is currently too tightly coupled into DefineDataParser + // so we do a rudimentary implementation to revisit later. + firstDimension.setLowerBound(IArrayDimension.UNBOUND_VALUE); + firstDimension.setUpperBound(IArrayDimension.UNBOUND_VALUE); + typedReturnVariable.addDimension(firstDimension); + while (tokens.peek().kind() != SyntaxKind.RPAREN && !tokens.isAtEnd()) + { + if (tokens.peek().kind() == SyntaxKind.COMMA) + { + var nextDimension = new ArrayDimension(); + nextDimension.setLowerBound(IArrayDimension.UNBOUND_VALUE); + nextDimension.setUpperBound(IArrayDimension.UNBOUND_VALUE); + typedReturnVariable.addDimension(nextDimension); + } + tokens.advance(); + } + } + tokens.advance(); // ) if (tokens.peek().kind() == SyntaxKind.DYNAMIC) { type = DataType.ofDynamicLength(type.format()); } naturalModule.setReturnType(type); - var typedReturnVariable = new TypedVariableNode(functionReturnVariable); typedReturnVariable.setType(new VariableType(type)); functionReturnVariable = typedReturnVariable; } diff --git a/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java b/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java index 08010e6c7..d0b133f4a 100644 --- a/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java +++ b/libs/natparse/src/test/java/org/amshove/natparse/parsing/NaturalParserShould.java @@ -9,6 +9,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +@SuppressWarnings("DataFlowIssue") class NaturalParserShould extends ParserIntegrationTest { @Test @@ -81,7 +82,28 @@ void addTheFunctionAsVariableToItsDefineData(@ProjectName("naturalParserTests") assertThat(variable).as("Function name as variable not found").isNotNull(); assertThat(variable.type().format()).isEqualTo(DataFormat.NUMERIC); assertThat(variable.type().length()).isEqualTo(12.7); + } + @Test + void parseTheFunctionReturnDimensions(@ProjectName("naturalParserTests") NaturalProject project) + { + var module = parse(project.findModule("TEST", "FUNC1DIM")); + assertThat(module).isInstanceOf(IFunction.class); + var function = (IFunction) module; + var variable = (ITypedVariableNode) function.defineData().findVariable("FUNC1DIM"); + assertThat(variable).as("Function name as variable not found").isNotNull(); + assertThat(variable.dimensions()).hasSize(1); + } + + @Test + void parseTheFunctionReturnDimensionsForMultipleDimensions(@ProjectName("naturalParserTests") NaturalProject project) + { + var module = parse(project.findModule("TEST", "FUNC2DIM")); + assertThat(module).isInstanceOf(IFunction.class); + var function = (IFunction) module; + var variable = (ITypedVariableNode) function.defineData().findVariable("FUNC2DIM"); + assertThat(variable).as("Function name as variable not found").isNotNull(); + assertThat(variable.dimensions()).hasSize(2); } @Test diff --git a/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNC1DIM.NS7 b/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNC1DIM.NS7 new file mode 100644 index 000000000..5ed0c7056 --- /dev/null +++ b/libs/natparse/src/test/resources/projects/naturalParserTests/Natural-Libraries/TEST/FUNC1DIM.NS7 @@ -0,0 +1,17 @@ +* >Natural Source Header 000000 +* :Mode S +* :CP +* Natural Source Header 000000 +* :Mode S +* :CP +*