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..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 @@ -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) { @@ -76,12 +84,37 @@ 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); + typedReturnVariable.setType(new VariableType(type)); + functionReturnVariable = typedReturnVariable; } advanceToDefineData(tokens); break; @@ -97,6 +130,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/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/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..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 @@ -2,12 +2,14 @@ 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; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +@SuppressWarnings("DataFlowIssue") class NaturalParserShould extends ParserIntegrationTest { @Test @@ -68,6 +70,42 @@ 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 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 void reportADiagnosticsForUnreferencedVariablesInFunctions(@ProjectName("variablereferencetests") NaturalProject project) { 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 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 +*