From 2fd5ecf8d68587c3e70f177752d73b9acd621d2d Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Mon, 16 Mar 2026 08:31:16 -0400 Subject: [PATCH] Work On Derived Types 1. Add support for field access 2. Add support for proper type checking of array access 3. Add tests for nested field and array access Signed-off-by: Will Hawkins --- Sources/Common/ProgramTypes.swift | 144 ++++++--- Sources/P4Compiler/Expression.swift | 104 ++++++- Sources/P4Compiler/Statement.swift | 10 +- Sources/P4Compiler/Types.swift | 6 +- Sources/P4Lang/Expressions.swift | 17 +- Sources/P4Runtime/Expressions.swift | 34 +- Sources/P4Runtime/Statements.swift | 2 +- Tests/p4rseTests/ScopeTests.swift | 20 +- Tests/p4rseTests/TypeTests.swift | 38 +++ Tests/p4rseTests/ValueTypeParserTests.swift | 326 +++++++++++++++++++- 10 files changed, 629 insertions(+), 72 deletions(-) create mode 100644 Tests/p4rseTests/TypeTests.swift diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index 579d9dc..3c098dc 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -27,6 +27,10 @@ public class Identifier: CustomStringConvertible, Equatable, Hashable { self.name = name } + public init(id: Identifier) { + self.name = id.name + } + public var description: String { return "\(name)" } @@ -38,13 +42,18 @@ public class Identifier: CustomStringConvertible, Equatable, Hashable { /// A P4 identifier public class TypedIdentifier: Identifier { - public var parsed_type: P4Type + public var type: P4Type public init(name: String, withType type: P4Type) { - self.parsed_type = type + self.type = type super.init(name: name) } + public init(id: Identifier, withType type: P4Type) { + self.type = type + super.init(id: id) + } + public override var description: String { return "\(name)" } @@ -73,23 +82,61 @@ public class Variable: TypedIdentifier { } } +public typealias P4StructFieldIdentifier = TypedIdentifier + +public struct P4StructFields: Sequence, CustomStringConvertible, Equatable { + public typealias Element = [P4StructFieldIdentifier].Iterator.Element + + public typealias Iterator = [P4StructFieldIdentifier].Iterator + + public func makeIterator() -> Iterator { + return self.fields.makeIterator() + } + + let fields: [P4StructFieldIdentifier] + + public init(_ fields: [P4StructFieldIdentifier]) { + self.fields = fields + } + + public var description: String { + return self.fields.map { field in + field.name + }.joined(separator: ",") + } + + public func get_field_type(_ field: Identifier) -> P4Type? { + if let found_field = self.fields.makeIterator().first(where: { current in + return current.name == field.name + }) { + return found_field.type + } + return .none + } + + public func count() -> Int { + return self.fields.count + } +} + /// The type for a P4 struct public struct P4Struct: P4Type { - public let name: String - // The type of the struct created is always anonymous. - public static func create() -> any P4Type { - return P4Struct() + public let name: Identifier + public let fields: P4StructFields + + public init(withName name: Identifier, andFields fields: P4StructFields) { + self.name = name + self.fields = fields } - public init(withName name: String) { - self.name = name - } public init() { - self.name = "" + self.name = Identifier(name: "") + self.fields = P4StructFields([]) } + public var description: String { - return "Struct \(self.name)" + return "Struct \(self.name) with fields: \(self.fields)" } public func eq(rhs: P4Type) -> Bool { @@ -101,21 +148,10 @@ public struct P4Struct: P4Type { } } -/// The field of a P4 struct -public struct P4StructField { - public let name: TypedIdentifier - public let type: P4Type - - public init(withName name: TypedIdentifier, withType type: P4Type) { - self.name = name - self.type = type - } -} - /// An instance of a P4 struct public class P4StructValue: P4Value { public func type() -> any P4Type { - return P4Struct() + return self.stype } public func eq(rhs: any P4Value) -> Bool { @@ -126,18 +162,37 @@ public class P4StructValue: P4Value { return "Struct" } - public let fields: [P4StructField] - public init(withFields fields: [P4StructField]) { - self.fields = fields + public let stype: P4Struct + public let values: [P4Value?] + + public convenience init(withType type: P4Struct) { + self.init(withType: type, andInitializers: []) + } + + public init(withType type: P4Struct, andInitializers initializers: [P4Value]) { + var values: [P4Value?] = Array(repeating: .none, count: type.fields.count()) + + for i in 0.. P4Value? { + for field_idx in 0.. any P4Type { - return P4Boolean() - } + public init() {} public var description: String { return "Boolean" } @@ -174,9 +229,8 @@ public class P4BooleanValue: P4Value { /// A P4 int type public struct P4Int: P4Type { - public static func create() -> any P4Type { - return P4Int() - } + public init() {} + public var description: String { return "Int" } @@ -216,10 +270,7 @@ public class P4IntValue: P4Value { /// A P4 string type public struct P4String: P4Type { - - public static func create() -> any P4Type { - return P4String() - } + public init() {} public var description: String { return "String" } @@ -258,13 +309,20 @@ public class Packet { /// A P4 array type public struct P4Array: P4Type { - - public static func create() -> any P4Type { - return P4Array() + public init(withValueType vtype: P4Type) { + self.vtype = vtype } + + let vtype: P4Type + + public func value_type() -> P4Type { + return self.vtype + } + public var description: String { return "Array" } + public func eq(rhs: any P4Type) -> Bool { return switch rhs { case is P4Array: true @@ -276,12 +334,14 @@ public struct P4Array: P4Type { /// An instance of a P4 array public class P4ArrayValue: P4Value { public func type() -> any P4Type { - return P4Array() + return P4Array(withValueType: self.vtype) } let value: [EvaluatableExpression] + let vtype: P4Type - public init(withValue value: [EvaluatableExpression]) { + public init(withType type: P4Type, withValue value: [EvaluatableExpression]) { + self.vtype = type self.value = value } diff --git a/Sources/P4Compiler/Expression.swift b/Sources/P4Compiler/Expression.swift index 02ec29f..71f0617 100644 --- a/Sources/P4Compiler/Expression.swift +++ b/Sources/P4Compiler/Expression.swift @@ -110,7 +110,7 @@ struct Expression { let localElementsParsers: [CompilableExpression.Type] = [ P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self, - BinaryOperatorExpression.self, ArrayAccessExpression.self, + BinaryOperatorExpression.self, ArrayAccessExpression.self, FieldAccessExpression.self, ] for le_parser in localElementsParsers { @@ -307,7 +307,7 @@ extension BinaryOperatorExpression: CompilableExpression { return .Ok( BinaryOperatorExpression( - withEvaluator: ("Binary Equal", P4Boolean.create(), binary_equal_operator_evaluator), + withEvaluator: ("Binary Equal", P4Boolean(), binary_equal_operator_evaluator), withLhs: left_hand_side, withRhs: right_hand_side)) } } @@ -370,12 +370,110 @@ extension ArrayAccessExpression: CompilableExpression { return Result.Error(maybe_array_identifier.error()!) } + let maybe_array_type = array_identifier.type() + guard let array_type = maybe_array_type as? P4Array else { + return Result.Error( + ErrorOnNode( + node: array_access_identifier_node, + withError: "\(array_identifier) does not name an array type") + ) + } + let maybe_array_indexor = Expression.Compile( node: array_access_indexor_node, withContext: context) guard case Result.Ok(let array_indexor) = maybe_array_indexor else { return Result.Error(maybe_array_indexor.error()!) } - return .Ok(ArrayAccessExpression(withName: array_identifier, withIndexor: array_indexor)) + return .Ok( + ArrayAccessExpression( + withName: array_identifier, withType: array_type, withIndexor: array_indexor)) + } +} + +extension FieldAccessExpression: CompilableExpression { + static func compile( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Common.Result<(any Common.EvaluatableExpression)?> { + let expression = node.child(at: 0)! + + #SkipUnlessNodeType( + node: expression, type: "fieldAccessExpression") + + let field_access_expression_node = expression + + var currentChildIdx = 0 + var currentChildIdxSafe = 1 + var currentChild: Node? = .none + + // What is the "name" of the struct? + if field_access_expression_node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Malformed field access expression")) + } + currentChild = expression.child(at: currentChildIdx) + + #RequireNodeType( + node: currentChild!, type: "expression", + nice_type_name: "struct identifier expression") + let struct_identifier_node = currentChild! + + // Check for the . + currentChildIdx = currentChildIdx + 1 + currentChildIdxSafe = currentChildIdxSafe + 1 + if field_access_expression_node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing . for field access expression") + ) + } + + // What is the field of the struct? + currentChildIdx = currentChildIdx + 1 + currentChildIdxSafe = currentChildIdxSafe + 1 + if field_access_expression_node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing field name for field access expression") + ) + } + currentChild = field_access_expression_node.child(at: currentChildIdx) + + #RequireNodeType( + node: currentChild!, type: "identifier", + nice_type_name: "field name") + + let field_name_node = currentChild! + + // Make sure that the identifier really identifies a struct. + let maybe_struct_identifier = Expression.Compile( + node: struct_identifier_node, withContext: context) + guard case Result.Ok(let struct_identifier) = maybe_struct_identifier else { + return Result.Error(maybe_struct_identifier.error()!) + } + guard let struct_type = struct_identifier.type() as? P4Struct else { + return .Error( + ErrorOnNode( + node: struct_identifier_node, + withError: "\(struct_identifier_node.text!) does not have struct type")) + } + + let maybe_field_name = Identifier.Compile( + node: field_name_node, withContext: context) + guard case Result.Ok(let field_name) = maybe_field_name else { + return Result.Error(maybe_field_name.error()!) + } + + // Make sure that the field is valid for the struct type. + let maybe_field_type = struct_type.fields.get_field_type(field_name) + guard let field_type = maybe_field_type else { + return .Error( + ErrorOnNode( + node: field_name_node, + withError: "\(field_name) is not a valid field for struct with type \(struct_type)")) + } + + return .Ok( + FieldAccessExpression( + withStruct: struct_identifier, + withField: P4StructFieldIdentifier(id: field_name, withType: field_type))) } } diff --git a/Sources/P4Compiler/Statement.swift b/Sources/P4Compiler/Statement.swift index 7fbac14..eb8801d 100644 --- a/Sources/P4Compiler/Statement.swift +++ b/Sources/P4Compiler/Statement.swift @@ -199,14 +199,12 @@ extension VariableDeclarationStatement: CompilableStatement { Error(withMessage: "Could not parse variable name")) } + let maybe_parsed_rvalue = Expression.Compile(node: rvalue, withContext: context) + guard - case .Ok(let parsed_rvalue) = Expression.Compile( - node: rvalue, withContext: context) + case .Ok(let parsed_rvalue) = maybe_parsed_rvalue else { - return Result.Error( - Error( - withMessage: - "Could not parse initial value expression in a variable declaration statement")) + return .Error(maybe_parsed_rvalue.error()!) } guard case .Ok(let declaration_p4_type) = Types.CompileBasicType(type: typeref.text!) else { diff --git a/Sources/P4Compiler/Types.swift b/Sources/P4Compiler/Types.swift index 4a05e5e..7bcd3f3 100644 --- a/Sources/P4Compiler/Types.swift +++ b/Sources/P4Compiler/Types.swift @@ -24,19 +24,19 @@ import TreeSitterP4 extension P4Boolean: CompilableType { public static func CompileType(type: String) -> Common.Result<(any Common.P4Type)?> { - return type == "bool" ? .Ok(P4Boolean.create()) : .Ok(.none) + return type == "bool" ? .Ok(P4Boolean()) : .Ok(.none) } } extension P4Int: CompilableType { public static func CompileType(type: String) -> Common.Result<(any Common.P4Type)?> { - return type == "int" ? .Ok(P4Int.create()) : .Ok(.none) + return type == "int" ? .Ok(P4Int()) : .Ok(.none) } } extension P4String: CompilableType { public static func CompileType(type: String) -> Common.Result<(any Common.P4Type)?> { - return type == "string" ? .Ok(P4String.create()) : .Ok(.none) + return type == "string" ? .Ok(P4String()) : .Ok(.none) } } public struct Types { diff --git a/Sources/P4Lang/Expressions.swift b/Sources/P4Lang/Expressions.swift index 2589640..db8a84f 100644 --- a/Sources/P4Lang/Expressions.swift +++ b/Sources/P4Lang/Expressions.swift @@ -76,9 +76,24 @@ public struct BinaryOperatorExpression { public struct ArrayAccessExpression { public let indexor: EvaluatableExpression public let name: EvaluatableExpression + public let type: P4Array - public init(withName name: EvaluatableExpression, withIndexor indexor: EvaluatableExpression) { + public init( + withName name: EvaluatableExpression, withType type: P4Array, + withIndexor indexor: EvaluatableExpression + ) { self.name = name + self.type = type self.indexor = indexor } } + +public struct FieldAccessExpression { + public let field: P4StructFieldIdentifier + public let strct: EvaluatableExpression + + public init(withStruct strct: EvaluatableExpression, withField field: P4StructFieldIdentifier) { + self.strct = strct + self.field = field + } +} diff --git a/Sources/P4Runtime/Expressions.swift b/Sources/P4Runtime/Expressions.swift index de5b956..9a134ef 100644 --- a/Sources/P4Runtime/Expressions.swift +++ b/Sources/P4Runtime/Expressions.swift @@ -76,10 +76,16 @@ extension P4IntValue: EvaluatableExpression { } } +extension P4ArrayValue: EvaluatableExpression { + public func evaluate(execution: Common.ProgramExecution) -> Common.Result { + return .Ok(self) + } +} + // Variables are evaluatable because they can be looked up by identifiers. extension TypedIdentifier: EvaluatableExpression { public func type() -> any Common.P4Type { - return self.parsed_type + return self.type } public func evaluate(execution: Common.ProgramExecution) -> Result { @@ -139,6 +145,30 @@ extension ArrayAccessExpression: EvaluatableExpression { } public func type() -> any Common.P4Type { - return P4Int.create() + return self.type.value_type() + } +} + +extension FieldAccessExpression: EvaluatableExpression { + public func evaluate(execution: Common.ProgramExecution) -> Common.Result { + let maybe_struct = self.strct.evaluate(execution: execution) + guard case Result.Ok(let strct) = maybe_struct else { + return maybe_struct + } + + guard let struct_strct = strct as? P4StructValue else { + return Result.Error(Error(withMessage: "\(strct) does not identify a struct")) + } + + // TODO: Create a default value? + guard let value = struct_strct.get(field: self.field) else { + return .Error(Error(withMessage: "Missing value")) + } + + return .Ok(value) + } + + public func type() -> any Common.P4Type { + return self.field.type } } diff --git a/Sources/P4Runtime/Statements.swift b/Sources/P4Runtime/Statements.swift index 54e8e80..5e1a345 100644 --- a/Sources/P4Runtime/Statements.swift +++ b/Sources/P4Runtime/Statements.swift @@ -44,7 +44,7 @@ extension ConditionalStatement: EvaluatableStatement { guard case .Ok(let evaluated_condition) = self.condition.evaluate(execution: execution) else { return execution.setError(error: Error(withMessage: "Could not evaluate \(self.condition)")) } - if !evaluated_condition.type().eq(rhs: P4Boolean.create()) { + if !evaluated_condition.type().eq(rhs: P4Boolean()) { return execution.setError(error: Error(withMessage: "Condition expression is not a Boolean")) } if evaluated_condition.eq(rhs: P4BooleanValue.init(withValue: true)) { diff --git a/Tests/p4rseTests/ScopeTests.swift b/Tests/p4rseTests/ScopeTests.swift index b2a3074..62bb0eb 100644 --- a/Tests/p4rseTests/ScopeTests.swift +++ b/Tests/p4rseTests/ScopeTests.swift @@ -28,39 +28,39 @@ import TreeSitterP4 @Test func test_scope() async throws { let s = LexicalScope() - let s2 = s.declare(identifier: Identifier(name: "first"), withValue: P4Int.create()) + let s2 = s.declare(identifier: Identifier(name: "first"), withValue: P4Int()) let found_first = try! #require(s2.lookup(identifier: Identifier(name: "first"))) - #expect(found_first.eq(rhs: P4Int.create())) + #expect(found_first.eq(rhs: P4Int())) #expect(s2.count == 1) } @Test func test_scope_no_set() async throws { var ss = LexicalScopes().enter() - ss = ss.declare(identifier: Identifier(name: "first"), withValue: P4Int.create()) + ss = ss.declare(identifier: Identifier(name: "first"), withValue: P4Int()) ss = ss.enter() - ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4Boolean.create()) + ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4Boolean()) let found_first = try! #UseOkResult(ss.lookup(identifier: Identifier(name: "first"))) let found_second = try! #UseOkResult(ss.lookup(identifier: Identifier(name: "second"))) - #expect(found_first.eq(rhs: P4Int.create())) - #expect(found_second.eq(rhs: P4Boolean.create())) + #expect(found_first.eq(rhs: P4Int())) + #expect(found_second.eq(rhs: P4Boolean())) } @Test func test_scope_set() async throws { var ss = LexicalScopes().enter() let id = Identifier(name: "first") - let id_type = P4Int.create() + let id_type = P4Int() ss = ss.declare(identifier: id, withValue: id_type) ss = ss.enter() - ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4Boolean.create()) + ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4Boolean()) // Change the value of `first`. - ss = ss.set(identifier: id, withValue: P4String.create()) + ss = ss.set(identifier: id, withValue: P4String()) // Verify the change! let found = try! #UseOkResult(ss.lookup(identifier: id)) - #expect(found.eq(rhs: P4String.create())) + #expect(found.eq(rhs: P4String())) } diff --git a/Tests/p4rseTests/TypeTests.swift b/Tests/p4rseTests/TypeTests.swift new file mode 100644 index 0000000..780e80b --- /dev/null +++ b/Tests/p4rseTests/TypeTests.swift @@ -0,0 +1,38 @@ +// p4rse, Copyright 2026, Will Hawkins +// +// This file is part of p4rse. +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import Foundation +import Common +import Macros +import SwiftTreeSitter +import Testing +import TreeSitter +import TreeSitterP4 + +@testable import P4Compiler + +@Test func test_simple_struct() async throws { + let fields = P4StructFields([ + P4StructFieldIdentifier(name: "yesno", withType: P4Boolean()), + P4StructFieldIdentifier(name: "count", withType: P4Int()), + ]) + + let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields) + + #expect(struct_type.fields.count() == 2) + #expect(struct_type.fields == fields) +} \ No newline at end of file diff --git a/Tests/p4rseTests/ValueTypeParserTests.swift b/Tests/p4rseTests/ValueTypeParserTests.swift index e261e7c..4af3ffd 100644 --- a/Tests/p4rseTests/ValueTypeParserTests.swift +++ b/Tests/p4rseTests/ValueTypeParserTests.swift @@ -230,6 +230,30 @@ import TreeSitterP4 #expect(state_result == P4Lang.reject) } +@Test func test_expression_in_declaration_initializer_invalid_types2() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = ta[0]; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array(withValueType: P4Int())) + #expect( + #RequireErrorResult( + Error( + withMessage: + "{49, 22}: Failed to parse a statement element: Cannot initialize where_to (with type Boolean) from rvalue with type Int" + ), + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types))) +} + + @Test func test_array_access() async throws { let simple_parser_declaration = """ parser main_parser() { @@ -243,11 +267,11 @@ import TreeSitterP4 }; """ var test_types = LexicalScopes().enter() - test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array.create()) + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array(withValueType: P4Int())) var test_values = ValueScopes().enter() test_values = test_values.declare( identifier: Identifier(name: "ta"), - withValue: P4ArrayValue(withValue: [ + withValue: P4ArrayValue(withType: P4Int(), withValue: [ P4IntValue(withValue: 1), P4IntValue(withValue: 2), P4IntValue(withValue: 3), ])) let program = try #UseOkResult( @@ -258,6 +282,29 @@ import TreeSitterP4 #expect(state_result == P4Lang.accept) } +@Test func test_array_access_invalid_type() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = ta[1]; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Int()) + #expect( + #RequireErrorResult( + Error( + withMessage: "{49, 22}: Failed to parse a statement element: {65, 2}: ta does not name an array type" + ), + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + ) +} + @Test func test_array_access2() async throws { let simple_parser_declaration = """ parser main_parser() { @@ -271,11 +318,11 @@ import TreeSitterP4 }; """ var test_types = LexicalScopes().enter() - test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array.create()) + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array(withValueType: P4Int())) var test_values = ValueScopes().enter() test_values = test_values.declare( identifier: Identifier(name: "ta"), - withValue: P4ArrayValue(withValue: [ + withValue: P4ArrayValue(withType: P4Int(), withValue: [ P4IntValue(withValue: 1), P4IntValue(withValue: 2), P4IntValue(withValue: 3), ])) let program = try #UseOkResult( @@ -285,3 +332,274 @@ import TreeSitterP4 #expect(state_result == P4Lang.reject) } + +@Test func test_array_access3() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + transition select (ta[0] == 1) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array(withValueType: P4Int())) + var test_values = ValueScopes().enter() + test_values = test_values.declare( + identifier: Identifier(name: "ta"), + withValue: P4ArrayValue(withType: P4Int(), withValue: [ + P4IntValue(withValue: 1), P4IntValue(withValue: 2), P4IntValue(withValue: 3), + ])) + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.accept) +} + +@Test func test_array_access4() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + transition select (ta[1] == 2) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array(withValueType: P4Int())) + var test_values = ValueScopes().enter() + test_values = test_values.declare( + identifier: Identifier(name: "ta"), + withValue: P4ArrayValue(withType: P4Int(), withValue: [ + P4IntValue(withValue: 1), P4IntValue(withValue: 2), P4IntValue(withValue: 3), + ])) + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.accept) +} + +@Test func test_array_access_nested() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + int where_to = ta[0][0]; + transition select (where_to == 5) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array(withValueType: P4Array(withValueType: P4Int()))) + var test_values = ValueScopes().enter() + + let nested = P4ArrayValue( + withType: P4Int(), + withValue: [P4IntValue(withValue: 5), P4IntValue(withValue: 2), P4IntValue(withValue: 3)]) + + test_values = test_values.declare( + identifier: Identifier(name: "ta"), + withValue: P4ArrayValue(withType: P4Array(withValueType: P4Int()), withValue: [nested])) + + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + + #expect(state_result == P4Lang.accept) +} + +@Test func test_field_access() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = ts.yesno; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + let fields = P4StructFields([ + P4StructFieldIdentifier(name: "yesno", withType: P4Boolean()), + P4StructFieldIdentifier(name: "count", withType: P4Int()), + ]) + let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields) + test_types = test_types.declare(identifier: Identifier(name: "ts"), withValue: struct_type) + + var test_values = ValueScopes().enter() + test_values = test_values.declare( + identifier: Identifier(name: "ts"), + withValue: P4StructValue(withType: struct_type, andInitializers: [ + P4BooleanValue(withValue: true), + P4IntValue(withValue: 5), + ])) + + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + #expect(state_result == P4Lang.accept) +} + +@Test func test_field_access_opp() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = ts.yesno; + transition select (where_to) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + let fields = P4StructFields([ + P4StructFieldIdentifier(name: "yesno", withType: P4Boolean()), + P4StructFieldIdentifier(name: "count", withType: P4Int()), + ]) + let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields) + test_types = test_types.declare(identifier: Identifier(name: "ts"), withValue: struct_type) + + var test_values = ValueScopes().enter() + test_values = test_values.declare( + identifier: Identifier(name: "ts"), + withValue: P4StructValue(withType: struct_type, andInitializers: [ + P4BooleanValue(withValue: false), + P4IntValue(withValue: 5), + ])) + + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + #expect(state_result == P4Lang.reject) +} + + +@Test func test_field_access2() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + transition select (ts.count == 5) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + let fields = P4StructFields([ + P4StructFieldIdentifier(name: "yesno", withType: P4Boolean()), + P4StructFieldIdentifier(name: "count", withType: P4Int()), + ]) + let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields) + test_types = test_types.declare(identifier: Identifier(name: "ts"), withValue: struct_type) + + var test_values = ValueScopes().enter() + test_values = test_values.declare( + identifier: Identifier(name: "ts"), + withValue: P4StructValue(withType: struct_type, andInitializers: [ + P4BooleanValue(withValue: true), + P4IntValue(withValue: 5), + ])) + + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + #expect(state_result == P4Lang.accept) +} + +@Test func test_field_access2_opp() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + transition select (ts.count == 5) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + let fields = P4StructFields([ + P4StructFieldIdentifier(name: "yesno", withType: P4Boolean()), + P4StructFieldIdentifier(name: "count", withType: P4Int()), + ]) + let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields) + test_types = test_types.declare(identifier: Identifier(name: "ts"), withValue: struct_type) + + var test_values = ValueScopes().enter() + test_values = test_values.declare( + identifier: Identifier(name: "ts"), + withValue: P4StructValue(withType: struct_type, andInitializers: [ + P4BooleanValue(withValue: true), + P4IntValue(withValue: 8), + ])) + + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + #expect(state_result == P4Lang.reject) +} + +@Test func test_field_access_nested() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + int where_to = ts.ty.count; + transition select (where_to == 5) { + true: accept; + false: reject; + }; + } + }; + """ + var test_types = LexicalScopes().enter() + + let ty_fields = P4StructFields([ + P4StructFieldIdentifier(name: "yesno", withType: P4Boolean()), + P4StructFieldIdentifier(name: "count", withType: P4Int()), + ]) + let ty_struct_type = P4Struct(withName: Identifier(name: "nested"), andFields: ty_fields) + + let ts_fields = P4StructFields([P4StructFieldIdentifier(name: "ty", withType: ty_struct_type)]) + let ts_struct_type = P4Struct(withName: Identifier(name: "outer"), andFields: ts_fields) + + test_types = test_types.declare(identifier: Identifier(name: "ts"), withValue: ts_struct_type) + + var test_values = ValueScopes().enter() + + test_values = test_values.declare( + identifier: Identifier(name: "ts"), + withValue: P4StructValue( + withType: ts_struct_type, + andInitializers: [ + P4StructValue( + withType: ty_struct_type, + andInitializers: [ + P4BooleanValue(withValue: true), + P4IntValue(withValue: 5), + ]) + ])) + let program = try #UseOkResult( + Program.Compile(simple_parser_declaration, withGlobalTypes: test_types)) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) + let (state_result, _) = try! #UseOkResult(runtime.run()) + #expect(state_result == P4Lang.accept) +} \ No newline at end of file