From 114451e23a0ecf5c03f3b00df551d0d529c57ce4 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Mar 2026 10:30:48 +0000 Subject: [PATCH] implements PR-13.3 --- .../pbs/semantics/PbsFlowBodyAnalyzer.java | 307 +------------- .../semantics/PbsFlowStatementAnalyzer.java | 384 ++++++++++++++++++ 2 files changed, 389 insertions(+), 302 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStatementAnalyzer.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java index 50d4b51d..7e80e6f3 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java @@ -1,7 +1,6 @@ package p.studio.compiler.pbs.semantics; import p.studio.compiler.pbs.ast.PbsAst; -import p.studio.compiler.source.Span; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.Diagnostics; import p.studio.utilities.structures.ReadOnlyList; @@ -15,6 +14,10 @@ final class PbsFlowBodyAnalyzer { private final PbsFlowTypeOps typeOps = new PbsFlowTypeOps(); private final PbsFlowExpressionAnalyzer expressionAnalyzer = new PbsFlowExpressionAnalyzer(typeOps); private final PbsFlowCompletionAnalyzer completionAnalyzer = new PbsFlowCompletionAnalyzer(); + private final PbsFlowStatementAnalyzer statementAnalyzer = new PbsFlowStatementAnalyzer( + typeOps, + expressionAnalyzer, + this::analyzeAssignmentStatement); private final PbsFlowCallableBodyAnalyzer callableBodyAnalyzer = new PbsFlowCallableBodyAnalyzer( typeOps, completionAnalyzer, @@ -146,307 +149,7 @@ final class PbsFlowBodyAnalyzer { final PbsAst.Block block, final PbsFlowBodyContext context, final boolean valueContext) { - final var scope = context.scope().copy(); - final var scopedContext = context.withScope(scope); - for (final var statement : block.statements()) { - analyzeStatement(statement, scopedContext); - } - if (block.tailExpression() == null) { - return TypeView.unit(); - } - return expressionAnalyzer.analyzeExpression( - block.tailExpression(), - scope, - null, - context.returnType(), - context.resultErrorName(), - context.receiverType(), - context.model(), - context.diagnostics(), - ExprUse.VALUE, - valueContext, - this::analyzeBlock).type(); - } - - private void analyzeStatement( - final PbsAst.Statement statement, - final PbsFlowBodyContext context) { - final var scope = context.scope(); - final var returnType = context.returnType(); - final var resultErrorName = context.resultErrorName(); - final var receiverType = context.receiverType(); - final var model = context.model(); - final var diagnostics = context.diagnostics(); - if (statement instanceof PbsAst.LetStatement letStatement) { - final var expected = letStatement.explicitType() == null - ? null - : typeOps.typeFromTypeRef(letStatement.explicitType(), model, receiverType); - final var initializer = expressionAnalyzer.analyzeExpression( - letStatement.initializer(), - scope, - expected, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock); - scope.bind(letStatement.name(), expected == null ? initializer.type() : expected, !letStatement.isConst()); - return; - } - if (statement instanceof PbsAst.ReturnStatement returnStatement) { - if (returnStatement.value() != null) { - analyzeReturnStatement(returnStatement.value(), context); - } - return; - } - if (statement instanceof PbsAst.IfStatement ifStatement) { - final var condition = expressionAnalyzer.analyzeExpression( - ifStatement.condition(), - scope, - TypeView.bool(), - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock).type(); - if (!typeOps.isBool(condition)) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), - "If statement condition must have bool type", - ifStatement.condition().span()); - } - analyzeBlock(ifStatement.thenBlock(), context, false); - if (ifStatement.elseBlock() != null) { - analyzeBlock(ifStatement.elseBlock(), context, false); - } - if (ifStatement.elseIf() != null) { - analyzeStatement(ifStatement.elseIf(), context); - } - return; - } - if (statement instanceof PbsAst.ForStatement( - String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression, - PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body, - Span span - )) { - final var iteratorType = typeOps.typeFromTypeRef(type, model, receiverType); - final var fromType = expressionAnalyzer.analyzeExpression( - fromExpression, - scope, - iteratorType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock).type(); - final var untilType = expressionAnalyzer.analyzeExpression( - untilExpression, - scope, - iteratorType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock).type(); - if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), - "For-loop bounds must match declared iterator type", - span); - } - if (stepExpression != null) { - final var stepType = expressionAnalyzer.analyzeExpression( - stepExpression, - scope, - iteratorType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock).type(); - if (!typeOps.compatible(stepType, iteratorType)) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), - "For-loop step must match declared iterator type", - stepExpression.span()); - } - } - final var bodyScope = scope.copy(); - bodyScope.bind(iteratorName, iteratorType); - analyzeBlock(body, context.withScope(bodyScope), false); - return; - } - if (statement instanceof PbsAst.WhileStatement whileStatement) { - final var condition = expressionAnalyzer.analyzeExpression( - whileStatement.condition(), - scope, - TypeView.bool(), - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock).type(); - if (!typeOps.isBool(condition)) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(), - "While condition must have bool type", - whileStatement.condition().span()); - } - analyzeBlock(whileStatement.body(), context, false); - return; - } - if (statement instanceof PbsAst.ExpressionStatement expressionStatement) { - expressionAnalyzer.analyzeExpression( - expressionStatement.expression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - false, - this::analyzeBlock); - return; - } - if (statement instanceof PbsAst.AssignStatement assignStatement) { - analyzeAssignmentStatement(assignStatement, context); - } - } - - private void analyzeReturnStatement( - final PbsAst.Expression value, - final PbsFlowBodyContext context) { - final var root = unwrapGroup(value); - if (root instanceof PbsAst.OkExpr okExpr) { - analyzeReturnOk(okExpr, context); - return; - } - if (root instanceof PbsAst.ErrExpr errExpr) { - analyzeReturnErr(errExpr, context); - return; - } - expressionAnalyzer.analyzeExpression( - value, - context.scope(), - context.returnType(), - context.returnType(), - context.resultErrorName(), - context.receiverType(), - context.model(), - context.diagnostics(), - ExprUse.VALUE, - true, - this::analyzeBlock); - } - - private void analyzeReturnOk( - final PbsAst.OkExpr okExpr, - final PbsFlowBodyContext context) { - final var returnType = context.returnType(); - final var resultErrorName = context.resultErrorName(); - final var diagnostics = context.diagnostics(); - if (returnType.kind() != Kind.RESULT || resultErrorName == null) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(), - "'ok(...)' is only allowed when returning from a result callable", - okExpr.span()); - expressionAnalyzer.analyzeExpression( - okExpr.value(), - context.scope(), - null, - returnType, - resultErrorName, - context.receiverType(), - context.model(), - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock); - return; - } - - final var payloadType = returnType.inner(); - final var actualType = expressionAnalyzer.analyzeExpression( - okExpr.value(), - context.scope(), - payloadType, - returnType, - resultErrorName, - context.receiverType(), - context.model(), - diagnostics, - ExprUse.VALUE, - true, - this::analyzeBlock).type(); - if (!typeOps.compatible(actualType, payloadType)) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(), - "Payload in 'ok(...)' is incompatible with result payload type", - okExpr.value().span()); - } - } - - private void analyzeReturnErr( - final PbsAst.ErrExpr errExpr, - final PbsFlowBodyContext context) { - final var returnType = context.returnType(); - final var resultErrorName = context.resultErrorName(); - final var model = context.model(); - final var diagnostics = context.diagnostics(); - if (returnType.kind() != Kind.RESULT || resultErrorName == null) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(), - "'err(...)' is only allowed when returning from a result callable", - errExpr.span()); - return; - } - - if (!matchesTargetError(errExpr.errorPath(), resultErrorName, model)) { - Diagnostics.error(diagnostics, - PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(), - "Error label in 'err(...)' does not match enclosing result error type", - errExpr.errorPath().span()); - } - } - - private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) { - if (expression instanceof PbsAst.GroupExpr groupExpr) { - return unwrapGroup(groupExpr.expression()); - } - return expression; - } - - private boolean matchesTargetError( - final PbsAst.ErrorPath path, - final String resultErrorName, - final Model model) { - if (path == null || resultErrorName == null || path.segments().size() != 2) { - return false; - } - final var errorName = path.segments().getFirst(); - final var caseName = path.segments().get(1); - final var targetCases = model.errors.get(errorName); - return resultErrorName.equals(errorName) && targetCases != null && targetCases.contains(caseName); + return statementAnalyzer.analyzeBlock(block, context, valueContext); } private void analyzeAssignmentStatement( diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStatementAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStatementAnalyzer.java new file mode 100644 index 00000000..03069cd8 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStatementAnalyzer.java @@ -0,0 +1,384 @@ +package p.studio.compiler.pbs.semantics; + +import p.studio.compiler.pbs.ast.PbsAst; +import p.studio.compiler.source.Span; +import p.studio.compiler.source.diagnostics.Diagnostics; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprUse; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Model; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Scope; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView; + +final class PbsFlowStatementAnalyzer { + @FunctionalInterface + interface AssignmentStatementAnalyzer { + void analyze(PbsAst.AssignStatement assignStatement, PbsFlowBodyContext context); + } + + private final PbsFlowTypeOps typeOps; + private final PbsFlowExpressionAnalyzer expressionAnalyzer; + private final AssignmentStatementAnalyzer assignmentStatementAnalyzer; + + PbsFlowStatementAnalyzer( + final PbsFlowTypeOps typeOps, + final PbsFlowExpressionAnalyzer expressionAnalyzer, + final AssignmentStatementAnalyzer assignmentStatementAnalyzer) { + this.typeOps = typeOps; + this.expressionAnalyzer = expressionAnalyzer; + this.assignmentStatementAnalyzer = assignmentStatementAnalyzer; + } + + TypeView analyzeBlock( + final PbsAst.Block block, + final PbsFlowBodyContext context, + final boolean valueContext) { + final var scope = context.scope().copy(); + final var scopedContext = context.withScope(scope); + for (final var statement : block.statements()) { + analyzeStatement(statement, scopedContext); + } + if (block.tailExpression() == null) { + return TypeView.unit(); + } + return expressionAnalyzer.analyzeExpression( + block.tailExpression(), + scope, + null, + context.returnType(), + context.resultErrorName(), + context.receiverType(), + context.model(), + context.diagnostics(), + ExprUse.VALUE, + valueContext, + this::analyzeNestedBlock).type(); + } + + private TypeView analyzeNestedBlock( + final PbsAst.Block block, + final Scope outerScope, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics, + final boolean valueContext) { + return analyzeBlock( + block, + new PbsFlowBodyContext( + outerScope, + returnType, + resultErrorName, + receiverType, + model, + diagnostics), + valueContext); + } + + private void analyzeStatement( + final PbsAst.Statement statement, + final PbsFlowBodyContext context) { + final var scope = context.scope(); + final var returnType = context.returnType(); + final var resultErrorName = context.resultErrorName(); + final var receiverType = context.receiverType(); + final var model = context.model(); + final var diagnostics = context.diagnostics(); + if (statement instanceof PbsAst.LetStatement letStatement) { + final var expected = letStatement.explicitType() == null + ? null + : typeOps.typeFromTypeRef(letStatement.explicitType(), model, receiverType); + final var initializer = expressionAnalyzer.analyzeExpression( + letStatement.initializer(), + scope, + expected, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock); + scope.bind(letStatement.name(), expected == null ? initializer.type() : expected, !letStatement.isConst()); + return; + } + if (statement instanceof PbsAst.ReturnStatement returnStatement) { + if (returnStatement.value() != null) { + analyzeReturnStatement(returnStatement.value(), context); + } + return; + } + if (statement instanceof PbsAst.IfStatement ifStatement) { + final var condition = expressionAnalyzer.analyzeExpression( + ifStatement.condition(), + scope, + TypeView.bool(), + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock).type(); + if (!typeOps.isBool(condition)) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), + "If statement condition must have bool type", + ifStatement.condition().span()); + } + analyzeBlock(ifStatement.thenBlock(), context, false); + if (ifStatement.elseBlock() != null) { + analyzeBlock(ifStatement.elseBlock(), context, false); + } + if (ifStatement.elseIf() != null) { + analyzeStatement(ifStatement.elseIf(), context); + } + return; + } + if (statement instanceof PbsAst.ForStatement( + String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression, + PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body, + Span span + )) { + analyzeForStatement( + iteratorName, + type, + fromExpression, + untilExpression, + stepExpression, + body, + span, + context); + return; + } + if (statement instanceof PbsAst.WhileStatement whileStatement) { + final var condition = expressionAnalyzer.analyzeExpression( + whileStatement.condition(), + scope, + TypeView.bool(), + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock).type(); + if (!typeOps.isBool(condition)) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(), + "While condition must have bool type", + whileStatement.condition().span()); + } + analyzeBlock(whileStatement.body(), context, false); + return; + } + if (statement instanceof PbsAst.ExpressionStatement expressionStatement) { + expressionAnalyzer.analyzeExpression( + expressionStatement.expression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + false, + this::analyzeNestedBlock); + return; + } + if (statement instanceof PbsAst.AssignStatement assignStatement) { + assignmentStatementAnalyzer.analyze(assignStatement, context); + } + } + + private void analyzeForStatement( + final String iteratorName, + final PbsAst.TypeRef type, + final PbsAst.Expression fromExpression, + final PbsAst.Expression untilExpression, + final PbsAst.Expression stepExpression, + final PbsAst.Block body, + final Span span, + final PbsFlowBodyContext context) { + final var scope = context.scope(); + final var returnType = context.returnType(); + final var resultErrorName = context.resultErrorName(); + final var receiverType = context.receiverType(); + final var model = context.model(); + final var diagnostics = context.diagnostics(); + final var iteratorType = typeOps.typeFromTypeRef(type, model, receiverType); + final var fromType = expressionAnalyzer.analyzeExpression( + fromExpression, + scope, + iteratorType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock).type(); + final var untilType = expressionAnalyzer.analyzeExpression( + untilExpression, + scope, + iteratorType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock).type(); + if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), + "For-loop bounds must match declared iterator type", + span); + } + if (stepExpression != null) { + final var stepType = expressionAnalyzer.analyzeExpression( + stepExpression, + scope, + iteratorType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock).type(); + if (!typeOps.compatible(stepType, iteratorType)) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), + "For-loop step must match declared iterator type", + stepExpression.span()); + } + } + final var bodyScope = scope.copy(); + bodyScope.bind(iteratorName, iteratorType); + analyzeBlock(body, context.withScope(bodyScope), false); + } + + private void analyzeReturnStatement( + final PbsAst.Expression value, + final PbsFlowBodyContext context) { + final var root = unwrapGroup(value); + if (root instanceof PbsAst.OkExpr okExpr) { + analyzeReturnOk(okExpr, context); + return; + } + if (root instanceof PbsAst.ErrExpr errExpr) { + analyzeReturnErr(errExpr, context); + return; + } + expressionAnalyzer.analyzeExpression( + value, + context.scope(), + context.returnType(), + context.returnType(), + context.resultErrorName(), + context.receiverType(), + context.model(), + context.diagnostics(), + ExprUse.VALUE, + true, + this::analyzeNestedBlock); + } + + private void analyzeReturnOk( + final PbsAst.OkExpr okExpr, + final PbsFlowBodyContext context) { + final var returnType = context.returnType(); + final var resultErrorName = context.resultErrorName(); + final var diagnostics = context.diagnostics(); + if (returnType.kind() != Kind.RESULT || resultErrorName == null) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(), + "'ok(...)' is only allowed when returning from a result callable", + okExpr.span()); + expressionAnalyzer.analyzeExpression( + okExpr.value(), + context.scope(), + null, + returnType, + resultErrorName, + context.receiverType(), + context.model(), + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock); + return; + } + + final var payloadType = returnType.inner(); + final var actualType = expressionAnalyzer.analyzeExpression( + okExpr.value(), + context.scope(), + payloadType, + returnType, + resultErrorName, + context.receiverType(), + context.model(), + diagnostics, + ExprUse.VALUE, + true, + this::analyzeNestedBlock).type(); + if (!typeOps.compatible(actualType, payloadType)) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(), + "Payload in 'ok(...)' is incompatible with result payload type", + okExpr.value().span()); + } + } + + private void analyzeReturnErr( + final PbsAst.ErrExpr errExpr, + final PbsFlowBodyContext context) { + final var returnType = context.returnType(); + final var resultErrorName = context.resultErrorName(); + final var model = context.model(); + final var diagnostics = context.diagnostics(); + if (returnType.kind() != Kind.RESULT || resultErrorName == null) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(), + "'err(...)' is only allowed when returning from a result callable", + errExpr.span()); + return; + } + + if (!matchesTargetError(errExpr.errorPath(), resultErrorName, model)) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(), + "Error label in 'err(...)' does not match enclosing result error type", + errExpr.errorPath().span()); + } + } + + private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) { + if (expression instanceof PbsAst.GroupExpr groupExpr) { + return unwrapGroup(groupExpr.expression()); + } + return expression; + } + + private boolean matchesTargetError( + final PbsAst.ErrorPath path, + final String resultErrorName, + final Model model) { + if (path == null || resultErrorName == null || path.segments().size() != 2) { + return false; + } + final var errorName = path.segments().getFirst(); + final var caseName = path.segments().get(1); + final var targetCases = model.errors.get(errorName); + return resultErrorName.equals(errorName) && targetCases != null && targetCases.contains(caseName); + } +}