From d3234347877d64ef068077bbfc9363757c392a80 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Fri, 13 Mar 2026 08:26:35 -0400 Subject: [PATCH] Begin Implementation of Binary Operator Support Signed-off-by: Will Hawkins --- Sources/Common/ProgramTypes.swift | 1 + Sources/P4Compiler/Expression.swift | 66 +++++++++++- Sources/P4Lang/Expressions.swift | 16 +++ Sources/P4Runtime/Expressions.swift | 27 +++++ Tests/p4rseTests/RuntimeTests.swift | 98 +++++++++++++++++ Tests/p4rseTests/ValueTypeParserTests.swift | 113 ++++++++++++++++++++ 6 files changed, 320 insertions(+), 1 deletion(-) diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index 61f8934..f506da6 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -199,6 +199,7 @@ public class P4IntValue: P4Value { self.value = value } public func eq(rhs: P4Value) -> Bool { + print("Int value equal.") guard let int_rhs = rhs as? P4IntValue else { return false } diff --git a/Sources/P4Compiler/Expression.swift b/Sources/P4Compiler/Expression.swift index 48511bc..1052131 100644 --- a/Sources/P4Compiler/Expression.swift +++ b/Sources/P4Compiler/Expression.swift @@ -19,6 +19,7 @@ import Common import P4Lang import SwiftTreeSitter import TreeSitterP4 +import P4Runtime protocol CompilableExpression { static func compile( @@ -108,7 +109,7 @@ struct Expression { } let localElementsParsers: [CompilableExpression.Type] = [ - P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self, + P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self, BinaryOperatorExpression.self ] for le_parser in localElementsParsers { @@ -242,3 +243,66 @@ extension KeysetExpression: CompilableExpression { ) } } + +extension BinaryOperatorExpression: CompilableExpression { + static func compile( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Result<(EvaluatableExpression)?> { + let expression = node.child(at: 0)! + + #SkipUnlessNodeType(node: expression, type: "binaryOperatorExpression") + + var currentChildIdx = 0 + var currentChildIdxSafe = 1 + var currentChild: Node? = .none + + if expression.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Malformed binary operator expression")) + } + currentChild = expression.child(at: currentChildIdx) + + let binary_operator_expression_node = currentChild! + #RequireNodesType(nodes: binary_operator_expression_node, type: ["binaryEqualOperatorExpression"], nice_type_names: ["binary equal operator"]) + + if binary_operator_expression_node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing LHS for binary operator expression")) + } + currentChild = binary_operator_expression_node.child(at: currentChildIdx) + let left_hand_side_raw = currentChild! + + currentChildIdx = currentChildIdx + 1 + currentChildIdxSafe = currentChildIdxSafe + 1 + if binary_operator_expression_node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing binary operator for binary operator expression")) + } + currentChild = binary_operator_expression_node.child(at: currentChildIdx) + + + currentChildIdx = currentChildIdx + 1 + currentChildIdxSafe = currentChildIdxSafe + 1 + if binary_operator_expression_node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing binary operator for binary operator expression")) + } + currentChild = binary_operator_expression_node.child(at: currentChildIdx) + let right_hand_side_raw = currentChild! + + let maybe_left_hand_side = Expression.Compile(node: left_hand_side_raw, withContext: context) + guard case Result.Ok(let left_hand_side) = maybe_left_hand_side else { + return Result.Error(maybe_left_hand_side.error()!) + } + + let maybe_right_hand_side = Expression.Compile(node: right_hand_side_raw, withContext: context) + guard case Result.Ok(let right_hand_side) = maybe_right_hand_side else { + return Result.Error(maybe_right_hand_side.error()!) + } + + return .Ok( + BinaryOperatorExpression( + withEvaluator: ("Binary Equal", P4Boolean.create(), binary_equal_operator_evaluator), + withLhs: left_hand_side, withRhs: right_hand_side)) + } +} \ No newline at end of file diff --git a/Sources/P4Lang/Expressions.swift b/Sources/P4Lang/Expressions.swift index cc96ba5..6fc5c6e 100644 --- a/Sources/P4Lang/Expressions.swift +++ b/Sources/P4Lang/Expressions.swift @@ -56,3 +56,19 @@ public struct SelectExpression { withSelector: self.selector, withKeysetExpressions: new_kse) } } + +public typealias NamedBinaryOperatorEvaluator = (String, P4Type, (P4Value, P4Value) -> P4Value) +public struct BinaryOperatorExpression { + public let evaluator: NamedBinaryOperatorEvaluator + public let left: EvaluatableExpression + public let right: EvaluatableExpression + + public init( + withEvaluator evaluator: NamedBinaryOperatorEvaluator, withLhs lhs: EvaluatableExpression, + withRhs rhs: EvaluatableExpression + ) { + self.evaluator = evaluator + self.left = lhs + self.right = rhs + } +} \ No newline at end of file diff --git a/Sources/P4Runtime/Expressions.swift b/Sources/P4Runtime/Expressions.swift index 06afca1..a55f2c4 100644 --- a/Sources/P4Runtime/Expressions.swift +++ b/Sources/P4Runtime/Expressions.swift @@ -86,3 +86,30 @@ extension TypedIdentifier: EvaluatableExpression { return execution.scopes.lookup(identifier: self) } } + +public func binary_equal_operator_evaluator(left: P4Value, right: P4Value) -> P4Value { + if left.eq(rhs: right) { + return P4BooleanValue(withValue: true) + } + return P4BooleanValue(withValue: false) +} + +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 { + return maybe_evaluated_left + } + + let maybe_evaluated_right = self.right.evaluate(execution: execution) + guard case Result.Ok(let evaluated_right) = maybe_evaluated_right else { + return maybe_evaluated_right + } + + return Result.Ok(self.evaluator.2(evaluated_left, evaluated_right)) + } + + public func type() -> any Common.P4Type { + return self.evaluator.1 + } +} \ No newline at end of file diff --git a/Tests/p4rseTests/RuntimeTests.swift b/Tests/p4rseTests/RuntimeTests.swift index 2d33533..26c61b4 100644 --- a/Tests/p4rseTests/RuntimeTests.swift +++ b/Tests/p4rseTests/RuntimeTests.swift @@ -197,3 +197,101 @@ import TreeSitterP4 Error(withMessage: "No key matched the selector"), runtime.run())) } + +@Test func test_simple_parser_binary_operator_equal() async throws { + let simple = """ + parser main_parser() { + state start { + transition select (true == true) { + true: accept; + false: reject; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Compile(simple)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.accept) +} + +@Test func test_simple_parser_binary_operator_equal_not_equal() async throws { + let simple = """ + parser main_parser() { + state start { + transition select (true == false) { + true: accept; + false: reject; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Compile(simple)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.reject) +} + +@Test func test_simple_parser_binary_operator_equal_integer() async throws { + let simple = """ + parser main_parser() { + state start { + transition select (5 == 5) { + true: accept; + false: reject; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Compile(simple)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.accept) +} + +@Test func test_simple_parser_binary_operator_equal_not_equal_integer() async throws { + let simple = """ + parser main_parser() { + state start { + transition select (5 == 6) { + true: accept; + false: reject; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Compile(simple)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.reject) +} + +@Test func test_simple_parser_binary_operator_equal_invalid_types() async throws { + let simple = """ + parser main_parser() { + state start { + transition select (5 == true) { + true: accept; + false: reject; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Compile(simple)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + + // TODO: This test should throw an error. + + #expect(state_result == P4Lang.reject) +} \ No newline at end of file diff --git a/Tests/p4rseTests/ValueTypeParserTests.swift b/Tests/p4rseTests/ValueTypeParserTests.swift index 6000587..2cfcee0 100644 --- a/Tests/p4rseTests/ValueTypeParserTests.swift +++ b/Tests/p4rseTests/ValueTypeParserTests.swift @@ -19,6 +19,7 @@ import Common import Foundation import Macros import P4Runtime +import P4Lang import SwiftTreeSitter import Testing import TreeSitter @@ -116,3 +117,115 @@ import TreeSitterP4 ), Program.Compile(simple_parser_declaration))) } + +@Test func test_expression_in_declaration_initializer() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = 5 == 5 == true; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + let program = try #UseOkResult(Program.Compile(simple_parser_declaration)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + // 5 == 5 == true + // true == true + // true + #expect(state_result == P4Lang.accept) +} + +@Test func test_expression_in_declaration_initializer2() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = 5 == 5 == false; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + let program = try #UseOkResult(Program.Compile(simple_parser_declaration)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + // 5 == 5 == true + // true == false + // false + #expect(state_result == P4Lang.reject) +} + +@Test func test_expression_in_declaration_initializer_false() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = 6 == 5 == true; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + let program = try #UseOkResult(Program.Compile(simple_parser_declaration)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + // 6 == 5 == true + // false == true + // false + #expect(state_result == P4Lang.reject) +} + +@Test func test_expression_in_declaration_initializer_false2() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = 6 == 5 == false; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + let program = try #UseOkResult(Program.Compile(simple_parser_declaration)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + // 6 == 5 == false + // false == false + // true + #expect(state_result == P4Lang.accept) +} + +@Test func test_expression_in_declaration_initializer_invalid_types() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = false == 5 == true; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + let program = try #UseOkResult(Program.Compile(simple_parser_declaration)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + // TODO: This test should throw an error. + + // false == 5 == true + // false == true + // false + #expect(state_result == P4Lang.reject) +}