From 82c125e4d1158641c15271b8f9b549f95fababc3 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Thu, 16 Apr 2026 06:58:45 -0400 Subject: [PATCH] compiler, runtime, common: Support (in)out Parameters When a function is called, if there is an (in)out parameter, make sure that updated values are propogated to the calling function. Signed-off-by: Will Hawkins --- Sources/Common/Execution.swift | 6 + Sources/Common/P4Types.swift | 42 +++- Sources/Common/Protocols.swift | 2 +- Sources/P4Compiler/Common.swift | 4 +- Sources/P4Compiler/Statement.swift | 2 +- Sources/P4Lang/Common.swift | 11 +- Sources/P4Runtime/Common.swift | 54 +++++ Sources/P4Runtime/Expressions.swift | 204 ++++++++++-------- Sources/P4Runtime/Parser.swift | 62 +++--- Sources/P4Runtime/Protocols.swift | 3 +- Sources/P4Runtime/Statements.swift | 8 +- Tests/p4rseTests/Declarations.swift | 30 ++- .../ExpressionTests/FunctionCall.swift | 29 +++ Tests/p4rseTests/StructTests.swift | 4 +- 14 files changed, 322 insertions(+), 139 deletions(-) diff --git a/Sources/Common/Execution.swift b/Sources/Common/Execution.swift index e6d1541..bbf75d7 100644 --- a/Sources/Common/Execution.swift +++ b/Sources/Common/Execution.swift @@ -87,6 +87,12 @@ open class ProgramExecution: CustomStringConvertible { return new_pe } + public func replaceScopes(_ new_scopes: VarValueScopes) -> ProgramExecution { + let new_pe = ProgramExecution(copy: self) + new_pe.scopes = new_scopes + return new_pe + } + public func declare(identifier: Identifier, withValue value: P4Value) -> ProgramExecution { let new_pe = ProgramExecution(copy: self) let new_scopes = new_pe.scopes.declare(identifier: identifier, withValue: value) diff --git a/Sources/Common/P4Types.swift b/Sources/Common/P4Types.swift index e3a7ea0..fcf3afa 100644 --- a/Sources/Common/P4Types.swift +++ b/Sources/Common/P4Types.swift @@ -15,6 +15,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +public enum TypeCheckResults: Equatable { + case Ok + case ReadOnly + case WrongDirection + case IncompatibleTypes +} + public enum Direction: Equatable, CustomStringConvertible { case In case Out @@ -115,11 +122,11 @@ public struct P4Type: CustomStringConvertible { } public func update(removeAttribute attribute: P4TypeAttribute) -> P4Type { - return P4Type(self._data_type, self._attributes.update(addAttribute: attribute)) + return P4Type(self._data_type, self._attributes.update(removeAttribute: attribute)) } public func update(addAttribute attribute: P4TypeAttribute) -> P4Type { - return P4Type(self._data_type, self._attributes.update(removeAttribute: attribute)) + return P4Type(self._data_type, self._attributes.update(addAttribute: attribute)) } public func direction() -> Direction? { @@ -143,6 +150,37 @@ public struct P4Type: CustomStringConvertible { && self.dataType().eq(rhs: rhs.dataType()) } + public func assignable() -> TypeCheckResults { + if self.readOnly() { + return TypeCheckResults.ReadOnly + } + + if let direction = direction(), + direction == Direction.In + { + return TypeCheckResults.WrongDirection + } + return TypeCheckResults.Ok + } + + public func assignableFromType(_ rhs: P4Type) -> TypeCheckResults { + if !self.dataType().eq(rhs: rhs.dataType()) { + return TypeCheckResults.IncompatibleTypes + } + + if self.readOnly() { + return TypeCheckResults.ReadOnly + } + + if let direction = direction(), + direction == Direction.In + { + return TypeCheckResults.WrongDirection + } + + return TypeCheckResults.Ok + } + public static func ReadOnly(_ type: P4DataType) -> P4Type { return P4Type(type, P4TypeAttributes.ReadOnly()) } diff --git a/Sources/Common/Protocols.swift b/Sources/Common/Protocols.swift index bce3be1..03a5f2a 100644 --- a/Sources/Common/Protocols.swift +++ b/Sources/Common/Protocols.swift @@ -20,7 +20,7 @@ public protocol EvaluatableExpression { /// - Parameters /// - execution: The execution context in which to evaluate the expression /// - Returns: The value of expression - func evaluate(execution: ProgramExecution) -> Result + func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) func type() -> P4Type } diff --git a/Sources/P4Compiler/Common.swift b/Sources/P4Compiler/Common.swift index 2b7d92f..a292182 100644 --- a/Sources/P4Compiler/Common.swift +++ b/Sources/P4Compiler/Common.swift @@ -83,8 +83,8 @@ func parameter_list_compiler( // Otherwise, there should be one parameter left! switch Parameter.Compile(node: currentChild!, withContext: context) { - case .Ok(let (vds, updated_context)): - return Result.Ok((parameters.addParameter(vds), updated_context)) + case .Ok(let (parsed_parameter, updated_context)): + return Result.Ok((parameters.addParameter(parsed_parameter), updated_context)) case .Error(let e): return Result.Error(e) } } diff --git a/Sources/P4Compiler/Statement.swift b/Sources/P4Compiler/Statement.swift index 54cf3ce..4ffdebf 100644 --- a/Sources/P4Compiler/Statement.swift +++ b/Sources/P4Compiler/Statement.swift @@ -312,7 +312,7 @@ extension ReturnStatement: CompilableStatement { return switch Expression.Compile(node: expression_node, withContext: context) { case .Ok(let result): - if result.type().eq(context.expected_type!) { + if result.type().dataType().eq(rhs: context.expected_type!.dataType()) { .Ok((ReturnStatement(result), context)) } else { .Error( diff --git a/Sources/P4Lang/Common.swift b/Sources/P4Lang/Common.swift index adf0690..7500006 100644 --- a/Sources/P4Lang/Common.swift +++ b/Sources/P4Lang/Common.swift @@ -39,7 +39,16 @@ public struct Parameter: CustomStringConvertible, Equatable { /// Calculate whether the `argument` is compatible with this parameter. public func compatible(_ argument: Argument) -> Bool { let arg_type = argument.argument.type() - return arg_type.eq(self.type) + + // If the parameter is (in)out, then the argument must be an lvalue. + if let param_direction = self.type.direction(), + param_direction == Direction.In || param_direction == Direction.InOut + { + if !(argument.argument is EvaluatableLValueExpression) { + return false + } + } + return arg_type.dataType().eq(rhs: self.type.dataType()) } } diff --git a/Sources/P4Runtime/Common.swift b/Sources/P4Runtime/Common.swift index 7419abc..d486da3 100644 --- a/Sources/P4Runtime/Common.swift +++ b/Sources/P4Runtime/Common.swift @@ -17,3 +17,57 @@ import Common import P4Lang + +public func Call( + body: (ProgramExecution) -> (Result, ProgramExecution), withArguments args: ArgumentList, + withParameters params: ParameterList, inExecution execution: ProgramExecution +) -> (Result, ProgramExecution) { + + if case .Error(let e) = args.compatible(params) { + return (.Error(e), execution) + } + + var called_execution = execution.enter_scope() + + for (parameter, argument) in zip(params.parameters, args.arguments) { + let arg_idx = argument.index + let arg_value = argument.argument + let maybe_argument_value = arg_value.evaluate(execution: called_execution) + guard case (.Ok(let argument_value), let updated_execution) = maybe_argument_value else { + return ( + .Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)")), + called_execution.exit_scope() + ) + } + called_execution = updated_execution.declare( + identifier: parameter.name, withValue: argument_value) + } + + let (maybe_call_result, updated_execution) = body(called_execution) + guard case .Ok(let call_result) = maybe_call_result else { + return (.Error(maybe_call_result.error()!), updated_execution.exit_scope()) + } + + // Before returning, update the (in)out parameters! + var inout_scopes = updated_execution.exit_scope().scopes + + for (parameter, argument) in zip(params.parameters, args.arguments) { + if let param_direction = parameter.type.direction(), + param_direction == Direction.InOut || param_direction == Direction.Out { + // Let's make sure that it is an evaluatable l value! + guard let arg_lvalue = argument.argument as? EvaluatableLValueExpression else { + return (.Error(Error(withMessage: "(in)out parameter argument is not lvalue")), updated_execution.exit_scope()) + } + + guard case .Ok(let arg_new_value) = updated_execution.scopes.lookup(identifier: parameter.name) else { + return (.Error(Error(withMessage: "Could not get (in)out parameter value from scope")), updated_execution.exit_scope()) + } + + switch arg_lvalue.set(to: arg_new_value, inScopes: inout_scopes, duringExecution: updated_execution) { + case .Ok((let updated_scopes, _)): inout_scopes = updated_scopes + case .Error(let e): return (.Error(e), updated_execution.exit_scope()) + } + } + } + return (.Ok(call_result), updated_execution.replaceScopes(inout_scopes)) +} \ No newline at end of file diff --git a/Sources/P4Runtime/Expressions.swift b/Sources/P4Runtime/Expressions.swift index b09931c..9565eb3 100644 --- a/Sources/P4Runtime/Expressions.swift +++ b/Sources/P4Runtime/Expressions.swift @@ -19,8 +19,8 @@ import Common import P4Lang extension SelectCaseExpression: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Common.Result { - return execution.scopes.lookup(identifier: next_state_identifier) + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { + return (execution.scopes.lookup(identifier: next_state_identifier), execution) } public func type() -> P4Type { @@ -29,19 +29,19 @@ extension SelectCaseExpression: EvaluatableExpression { } extension SelectExpression: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Common.Result { + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { switch self.selector.evaluate(execution: execution) { - case .Ok(let selector_value): + case (.Ok(let selector_value), let updated_execution): for sce in self.case_expressions { - if case .Ok(let kse) = sce.key.evaluate(execution: execution), + if case (.Ok(let kse), let updated_execution) = sce.key.evaluate(execution: updated_execution), kse.eq(selector_value) { - let result = sce.evaluate(execution: execution) + let result = sce.evaluate(execution: updated_execution) return result } } - return .Error(Error(withMessage: "No key matched the selector")) - case .Error(let e): return .Error(e) + return (.Error(Error(withMessage: "No key matched the selector")), updated_execution) + case (.Error(let e), let updated_execution): return (.Error(e), updated_execution) } } @@ -56,8 +56,8 @@ extension TypedIdentifier: EvaluatableExpression { return self.type } - public func evaluate(execution: Common.ProgramExecution) -> Result { - return execution.scopes.lookup(identifier: self) + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { + return (execution.scopes.lookup(identifier: self), execution) } } @@ -81,14 +81,12 @@ extension TypedIdentifier: EvaluatableLValueExpression { return .Error(Error(withMessage: "Cannot assign to identifier not in scope")) } - if !type.eq(to.type()) { - return .Error( - Error( - withMessage: - "Cannot assign value with type \(to.type()) to identifier \(self) with type \(type)" - )) + return switch type.assignableFromType(to.type()) { + case TypeCheckResults.IncompatibleTypes: .Error( Error( withMessage: "Cannot assign value with type \(to.type()) to identifier \(self) with type \(type)")) + case TypeCheckResults.ReadOnly: .Error( Error( withMessage: "Cannot assign value with type \(to.type()) to identifier \(self) that is read only")) + case TypeCheckResults.WrongDirection: .Error( Error( withMessage: "Cannot assign value with type \(to.type()) to identifier \(self) that is in parameter")) + case TypeCheckResults.Ok: .Ok(()) } - return .Ok(()) } } @@ -196,18 +194,19 @@ public func binary_int_math_operator_checker( } extension BinaryOperatorExpression: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Common.Result { - let maybe_evaluated_left = self.left.evaluate(execution: execution) - guard case Result.Ok(let evaluated_left) = maybe_evaluated_left else { + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { + let updated_execution = execution + let maybe_evaluated_left = self.left.evaluate(execution: updated_execution) + guard case (.Ok(let evaluated_left), let updated_execution) = maybe_evaluated_left else { return maybe_evaluated_left } - let maybe_evaluated_right = self.right.evaluate(execution: execution) - guard case Result.Ok(let evaluated_right) = maybe_evaluated_right else { + let maybe_evaluated_right = self.right.evaluate(execution: updated_execution) + guard case (.Ok(let evaluated_right), let updated_execution) = maybe_evaluated_right else { return maybe_evaluated_right } - return Result.Ok(P4Value(self.evaluator.2(evaluated_left, evaluated_right))) + return (.Ok(P4Value(self.evaluator.2(evaluated_left, evaluated_right))), updated_execution) } public func type() -> P4Type { @@ -216,27 +215,28 @@ extension BinaryOperatorExpression: EvaluatableExpression { } extension ArrayAccessExpression: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Common.Result { - let maybe_name = self.name.evaluate(execution: execution) - guard case Result.Ok(let name) = maybe_name else { + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { + let updated_execution = execution + let maybe_name = self.name.evaluate(execution: updated_execution) + guard case (.Ok(let name), let updated_execution) = maybe_name else { return maybe_name } - let maybe_indexor = self.indexor.evaluate(execution: execution) - guard case Result.Ok(let indexor) = maybe_indexor else { + let maybe_indexor = self.indexor.evaluate(execution: updated_execution) + guard case (.Ok(let indexor), let updated_execution) = maybe_indexor else { return maybe_indexor } guard let indexor_int = indexor.dataValue() as? P4IntValue else { - return Result.Error(Error(withMessage: "\(indexor) cannot index an array")) + return (.Error(Error(withMessage: "\(indexor) cannot index an array")), updated_execution) } guard let array = name.dataValue() as? P4ArrayValue else { - return Result.Error(Error(withMessage: "\(name) does not name an array")) + return (.Error(Error(withMessage: "\(name) does not name an array")), updated_execution) } let accessed = array.access(indexor_int.access()) - return .Ok(accessed) + return (.Ok(accessed), updated_execution) } public func type() -> P4Type { @@ -250,20 +250,20 @@ extension ArrayAccessExpression: EvaluatableLValueExpression { duringExecution execution: ProgramExecution ) -> Common.Result<(Common.VarValueScopes, P4Value)> { - let maybe_value = self.name.evaluate(execution: execution) - guard case .Ok(let value) = maybe_value else { - return Result.Error( - Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.error()!)")) + let updated_execution = execution + let maybe_value = self.name.evaluate(execution: updated_execution) + guard case (.Ok(let value), let updated_execution) = maybe_value else { + return .Error(Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.0.error()!)")) } guard let array_value = value.dataValue() as? P4ArrayValue else { return Result.Error(Error(withMessage: "\(self.name) does not identify an array")) } // Now, get the indexor! - let maybe_indexor_value = self.indexor.evaluate(execution: execution) - guard case .Ok(let indexor_value) = maybe_indexor_value else { + let maybe_indexor_value = self.indexor.evaluate(execution: updated_execution) + guard case (.Ok(let indexor_value), let updated_execution) = maybe_indexor_value else { return Result.Error( - Error(withMessage: "\(self.indexor) cannot be evaluated: \(maybe_indexor_value.error()!)")) + Error(withMessage: "\(self.indexor) cannot be evaluated: \(maybe_indexor_value.0.error()!)")) } guard let indexor_int = indexor_value.dataValue() as? P4IntValue else { return Result.Error(Error(withMessage: "\(self.indexor) cannot be used to index an array")) @@ -282,42 +282,63 @@ extension ArrayAccessExpression: EvaluatableLValueExpression { } let array_lvalue = self.name as! EvaluatableLValueExpression - return array_lvalue.set(to: updated_array_value, inScopes: scopes, duringExecution: execution) + return array_lvalue.set(to: updated_array_value, inScopes: scopes, duringExecution: updated_execution) } public func check( to: any Common.EvaluatableExpression, inScopes scopes: Common.VarTypeScopes ) -> Common.Result<()> { - if !self.type.value_type().eq(to.type()) { - return .Error( + return switch self.type.value_type().assignableFromType(to.type()) { + case TypeCheckResults.IncompatibleTypes: + .Error( Error( withMessage: "Cannot assign value of type \(to.type()) to array with values of type \(self.name.type())" )) + case TypeCheckResults.ReadOnly: + .Error( + Error( + withMessage: "Cannot assign value of type \(to.type()) to array \(self) that is read only" + )) + case TypeCheckResults.WrongDirection: + .Error( + Error( + withMessage: + "Cannot assign value of type \(to.type()) to array \(self) that is in parameter")) + case TypeCheckResults.Ok: + // Now, check the type of the array itself. + switch self.name.type().assignable() { + case TypeCheckResults.ReadOnly: + .Error(Error(withMessage: "Cannot assign to array \(self) that is read only")) + case TypeCheckResults.WrongDirection: + .Error(Error(withMessage: "Cannot assign to array \(self) that is in parameter")) + case TypeCheckResults.Ok: .Ok(()) + default: .Error(Error(withMessage: "Cannot assign to array \(self)")) + } } - return .Ok(()) } } extension FieldAccessExpression: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Common.Result { + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { - let maybe_struct = self.strct.evaluate(execution: execution) - guard case Result.Ok(let strct) = maybe_struct else { + let updated_execution = execution + let maybe_struct = self.strct.evaluate(execution: updated_execution) + guard case (.Ok(let strct), let updated_execution) = maybe_struct else { return maybe_struct } guard let struct_strct = strct.dataValue() as? P4StructValue else { - return Result.Error(Error(withMessage: "\(strct) does not identify a struct")) + return (.Error(Error(withMessage: "\(strct) does not identify a struct")), updated_execution) } // TODO: Create a default value? guard let value = struct_strct.get(field: self.field) else { - return .Error(Error(withMessage: "Missing value")) + return (.Error(Error(withMessage: "Missing value")), updated_execution) } - return .Ok(value) + return (.Ok(value), updated_execution) } public func type() -> P4Type { @@ -335,15 +356,16 @@ extension FieldAccessExpression: EvaluatableLValueExpression { // where strct_id expands to // (identifier.field_id1.field_id2...).field_id = new_field_value + let updated_execution = execution // First, evaluate strct_id and make sure that it names a struct. - let maybe_value = self.strct.evaluate(execution: execution) - guard case .Ok(let value) = maybe_value else { - return Result.Error( - Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.error()!)")) + let maybe_value = self.strct.evaluate(execution: updated_execution) + guard case (.Ok(let value), let updated_execution) = maybe_value else { + return .Error( + Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.0.error()!)")) } guard let struct_value = value.dataValue() as? P4StructValue else { - return Result.Error(Error(withMessage: "\(self.strct) does not identify a struct")) + return .Error(Error(withMessage: "\(self.strct) does not identify a struct")) } // Now we know that struct_id identifies a structure value. @@ -363,25 +385,47 @@ extension FieldAccessExpression: EvaluatableLValueExpression { // We use recursion here -- ultimately finding our way to a TypedIdentifier that // will update the scope. Pretty cool! let struct_lvalue = self.strct as! EvaluatableLValueExpression - return struct_lvalue.set(to: new_struct_value, inScopes: scopes, duringExecution: execution) + return struct_lvalue.set(to: new_struct_value, inScopes: scopes, duringExecution: updated_execution) } public func check( to: any Common.EvaluatableExpression, inScopes scopes: Common.VarTypeScopes ) -> Common.Result<()> { - - if !self.field.type.eq(to.type()) { - return .Error( + return switch self.field.type().assignableFromType(to.type()) { + case TypeCheckResults.IncompatibleTypes: + .Error( Error( withMessage: - "Cannot assign value of type \(to.type()) to field with type \(self.field.type)")) + "Cannot assign value of type \(to.type()) to field \(self.field) of type \(self.type())" + )) + case TypeCheckResults.ReadOnly: + .Error( + Error( + withMessage: + "Cannot assign value of type \(to.type()) to field \(self.field) that is read only" + )) + case TypeCheckResults.Ok: + // Now, check the type of the struct itself. + switch self.strct.type().assignable() { + case TypeCheckResults.ReadOnly: + .Error( + Error( + withMessage: "Cannot assign to field \(self.field) of \(self.strct) that is read only")) + case TypeCheckResults.WrongDirection: + .Error( + Error( + withMessage: + "Cannot assign to field \(self.field) of \(self.strct) that is in parameter")) + case TypeCheckResults.Ok: .Ok(()) + default: .Error(Error(withMessage: "Cannot assign to field \(self.field) of \(self.strct)")) + } + default: .Error(Error(withMessage: "Cannot assign to field \(self.field) of \(self.strct)")) } - return .Ok(()) } } extension KeysetExpression: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Result { + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { return self.key.evaluate(execution: execution) } @@ -391,38 +435,24 @@ extension KeysetExpression: EvaluatableExpression { } extension FunctionCall: EvaluatableExpression { - public func evaluate(execution: Common.ProgramExecution) -> Common.Result { + public func evaluate(execution: Common.ProgramExecution) -> (Common.Result, ProgramExecution) { guard let body = self.callee.body else { - return .Error(Error(withMessage: "No body for called function (\(self.callee.name))")) + return (.Error(Error(withMessage: "No body for called function (\(self.callee.name))")), execution) } - // Put the arguments into scope - - var called_execution = execution.enter_scope() - for (parameter, argument) in zip(self.callee.params.parameters, arguments.arguments) { - let arg_idx = argument.index - let arg_value = argument.argument - let maybe_argument_value = arg_value.evaluate(execution: called_execution) - guard case .Ok(let argument_value) = maybe_argument_value else { - return .Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)")) + let call_body: (ProgramExecution) -> (Result, ProgramExecution) = { (execution: ProgramExecution) in + let (control_flow, updated_execution) = body.evaluate(execution: execution) + return switch control_flow { + case ControlFlow.Return(.some(let value)): (.Ok(value), updated_execution) + default: + (.Error(Error(withMessage: "No value returned from called function (\(self.callee.name))")), execution) } - called_execution = called_execution.declare( - identifier: parameter.name, withValue: argument_value) } - let (control_flow, _) = body.evaluate(execution: called_execution) - - return switch control_flow { - case ControlFlow.Return(let value): - if let value = value { - .Ok(value) - } else { - .Error(Error(withMessage: "No value returned from called function (\(self.callee.name))")) - } - default: - .Error(Error(withMessage: "No value returned from called function (\(self.callee.name))")) - } + return Call( + body: call_body, withArguments: self.arguments, withParameters: self.callee.params, + inExecution: execution) } public func type() -> P4Type { @@ -431,7 +461,7 @@ extension FunctionCall: EvaluatableExpression { } extension P4Value: EvaluatableExpression { - public func evaluate(execution: ProgramExecution) -> Result { - return .Ok(self) + public func evaluate(execution: ProgramExecution) -> (Result, ProgramExecution) { + return (.Ok(self), execution) } } diff --git a/Sources/P4Runtime/Parser.swift b/Sources/P4Runtime/Parser.swift index 3d5f4ce..f705c1e 100644 --- a/Sources/P4Runtime/Parser.swift +++ b/Sources/P4Runtime/Parser.swift @@ -20,19 +20,20 @@ import P4Lang extension ParserAssignmentStatement: EvaluatableStatement { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { - let result = self.value.evaluate(execution: execution) - guard case Result.Ok(let value) = result else { - return (ControlFlow.Error, execution.setError(error: result.error()!)) + let updated_execution = execution + let result = self.value.evaluate(execution: updated_execution) + guard case (.Ok(let value), let updated_execution) = result else { + return (ControlFlow.Error, execution.setError(error: result.0.error()!)) } let maybe_updated_scopes = self.lvalue.set( - to: value, inScopes: execution.scopes, duringExecution: execution) + to: value, inScopes: execution.scopes, duringExecution: updated_execution) guard case Result.Ok(let updated_scopes) = maybe_updated_scopes else { return (ControlFlow.Error, execution.setError(error: maybe_updated_scopes.error()!)) } execution.scopes = updated_scopes.0 - return (ControlFlow.Next, execution) + return (ControlFlow.Next, updated_execution) } } @@ -116,9 +117,8 @@ extension ParserStateSelectTransition: EvaluatableParserState { } } - let res = self.selectExpression.evaluate(execution: program) - - if case .Ok(let value) = res { + switch self.selectExpression.evaluate(execution: program) { + case (.Ok(let value), let program): if value.type().dataType().eq(rhs: self) { return (value.dataValue() as! EvaluatableParserState, program.exit_scope()) } else { @@ -128,10 +128,8 @@ extension ParserStateSelectTransition: EvaluatableParserState { error: Error(withMessage: "Select transition transitioned to a none state")) ) } + case (.Error(let e), let program): return (self, program.setError(error: e).exit_scope()) } - - program = program.setError(error: res.error()!).exit_scope() - return (self, program.exit_scope()) } public func done() -> Bool { @@ -143,7 +141,7 @@ extension ParserStateSelectTransition: EvaluatableParserState { } } -extension Parser: CallableExecution { +extension Parser: LibraryCallable { public typealias T = InstantiatedParserState public func call( execution: Common.ProgramExecution, arguments: P4Lang.ArgumentList @@ -178,34 +176,24 @@ extension Parser: CallableExecution { ) } - // Now that we are assured that there is a start state, - // let's set the arguments. - - if case .Error(let e) = arguments.compatible(self.parameters) { - return ( - reject, execution.setError(error: Error(withMessage: "Cannot call parser: \(e)")) - ) - } - - for (parameter, argument) in zip(self.parameters.parameters, arguments.arguments) { - let arg_idx = argument.index - let arg_value = argument.argument - let maybe_argument_value = arg_value.evaluate(execution: execution) - guard case .Ok(let argument_value) = maybe_argument_value else { - return ( - reject, - execution.setError( - error: Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)")) - ) + let call_body: (ProgramExecution) -> (Result, ProgramExecution) = { + (execution: ProgramExecution) in + var current_execution = execution + // Evaluate until the state is either accept or reject. + while !current_state.done() && !current_execution.hasError() { + (current_state, current_execution) = current_state.execute(program: current_execution) } - execution = execution.declare(identifier: parameter.name, withValue: argument_value) + return (.Ok(P4Value(AsInstantiatedParserState(current_state.state()))), current_execution) } - // Evaluate until the state is either accept or reject. - while !current_state.done() && !execution.hasError() { - (current_state, execution) = current_state.execute(program: execution) + return + switch Call( + body: call_body, withArguments: arguments, withParameters: parameters, inExecution: execution) + { + case (.Ok(let value), let updated_execution): + (value.dataValue() as! InstantiatedParserState, updated_execution) + case (.Error(let e), let updated_execution): + (reject, updated_execution.setError(error: Error(withMessage: "Cannot call parser: \(e)"))) } - - return (AsInstantiatedParserState(current_state.state()), execution.exit_scope()) } } diff --git a/Sources/P4Runtime/Protocols.swift b/Sources/P4Runtime/Protocols.swift index c8088fb..c131ea0 100644 --- a/Sources/P4Runtime/Protocols.swift +++ b/Sources/P4Runtime/Protocols.swift @@ -28,7 +28,8 @@ public protocol EvaluatableParserState: P4DataValue { func state() -> ParserState } -public protocol CallableExecution { +/// Defines an interface for P4 components that can be invoked directly by the p4rse library user +public protocol LibraryCallable { associatedtype T func call(execution: ProgramExecution, arguments: ArgumentList) -> (T, ProgramExecution) } diff --git a/Sources/P4Runtime/Statements.swift b/Sources/P4Runtime/Statements.swift index 0d47ad9..977ba53 100644 --- a/Sources/P4Runtime/Statements.swift +++ b/Sources/P4Runtime/Statements.swift @@ -41,7 +41,7 @@ extension BlockStatement: EvaluatableStatement { extension VariableDeclarationStatement: EvaluatableStatement { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { - guard case .Ok(let initial_value) = self.initializer.evaluate(execution: execution) else { + guard case (.Ok(let initial_value), let execution) = self.initializer.evaluate(execution: execution) else { return ( ControlFlow.Error, execution.setError(error: Error(withMessage: "Could not evaluate \(self.initializer)")) @@ -55,7 +55,7 @@ extension VariableDeclarationStatement: EvaluatableStatement { extension ConditionalStatement: EvaluatableStatement { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { - guard case .Ok(let evaluated_condition) = self.condition.evaluate(execution: execution) else { + guard case (.Ok(let evaluated_condition), let execution) = self.condition.evaluate(execution: execution) else { return ( ControlFlow.Error, execution.setError(error: Error(withMessage: "Could not evaluate \(self.condition)")) @@ -108,8 +108,8 @@ extension ExpressionStatement: EvaluatableStatement { extension ReturnStatement: EvaluatableStatement { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { return switch self.value.evaluate(execution: execution) { - case .Ok(let v): (ControlFlow.Return(v), execution) - case .Error(let e): (ControlFlow.Error, execution.setError(error: e)) + case (.Ok(let v), let execution): (ControlFlow.Return(v), execution) + case (.Error(let e), let execution): (ControlFlow.Error, execution.setError(error: e)) } } } diff --git a/Tests/p4rseTests/Declarations.swift b/Tests/p4rseTests/Declarations.swift index 5a99f20..f99936e 100644 --- a/Tests/p4rseTests/Declarations.swift +++ b/Tests/p4rseTests/Declarations.swift @@ -172,6 +172,34 @@ import TreeSitterP4 x = true; }; """ - #expect(#RequireOkResult(Program.Compile(simple_parser_declaration))) + #expect( + #RequireErrorResult( + Error( + withMessage: + "{56, 21}: Failed to parse a statement element: {63, 9}: Failed to parse a statement element: {63, 1}: Cannot assign value with type Boolean to identifier x that is in parameter" + ), + Program.Compile(simple_parser_declaration)) + ) +} + +@Test func test_function_declaration_with_parameters_and_direction_struct() async throws { + let simple_parser_declaration = """ + struct Testing { + bool yesno; + int count; + }; + bool functionb(in Testing x, out string y, inout int z) { + x.yesno = true; + }; + """ + #expect( + #RequireErrorResult( + Error( + withMessage: + "{113, 27}: Failed to parse a statement element: {120, 15}: Failed to parse a statement element: {120, 7}: Cannot assign to field yesno of x that is in parameter" + ), + Program.Compile(simple_parser_declaration)) + ) + } diff --git a/Tests/p4rseTests/ExpressionTests/FunctionCall.swift b/Tests/p4rseTests/ExpressionTests/FunctionCall.swift index 0a675ed..c8a6e37 100644 --- a/Tests/p4rseTests/ExpressionTests/FunctionCall.swift +++ b/Tests/p4rseTests/ExpressionTests/FunctionCall.swift @@ -83,6 +83,35 @@ import TreeSitterP4 #expect(AsInstantiatedParserState(state_result) == P4Lang.reject) } +@Test func test_function_call_scoped_name_collision_inout() async throws { + let simple_parser_declaration = """ + bool functionb(inout bool c) { + c = true; + return c; + }; + parser main_parser() { + state start { + bool c = false; + bool b = functionb(c); + transition select (c) { + false: reject; + true: accept; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Compile(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(parser.states.count() == 1) + + #expect(AsInstantiatedParserState(state_result) == P4Lang.accept) +} + + @Test func test_function_call_integer_return_value() async throws { let simple_parser_declaration = """ int functionb(int c) { diff --git a/Tests/p4rseTests/StructTests.swift b/Tests/p4rseTests/StructTests.swift index 1840d5e..5da57be 100644 --- a/Tests/p4rseTests/StructTests.swift +++ b/Tests/p4rseTests/StructTests.swift @@ -327,7 +327,7 @@ import TreeSitterP4 #expect( #RequireErrorResult( Error( - withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int to field with type Boolean" + withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int to field yesno of type Boolean" ), Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) ) @@ -457,7 +457,7 @@ import TreeSitterP4 #expect( #RequireErrorResult( Error( - withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field with type Int" + withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field count of type Int" ), Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) )