Refactor Type System

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-02-06 07:46:18 -05:00
parent bc700509c1
commit c3fdfb62e8
11 changed files with 395 additions and 199 deletions
+17
View File
@@ -10,3 +10,20 @@ Very, very alpha:
2. Limited programs can be evaluated. 2. Limited programs can be evaluated.
Please check back often! Please check back often!
### Building
#### Generating Documentation
To build the documentation:
```console
$ swift package generate-documentation
```
To preview the generated documentation:
```console
$ swift package swift package --disable-sandbox preview-documentation --target <some target>
```
For more information, see the [documentation for the Swift-DocC plugin](https://swiftlang.github.io/swift-docc-plugin/documentation/swiftdoccplugin/).
+145 -59
View File
@@ -15,85 +15,171 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
/// A P4 identifier
public class Identifier: CustomStringConvertible, Equatable { public class Identifier: CustomStringConvertible, Equatable {
var name: String var name: String
public init(name: String) { public init(name: String) {
self.name = name self.name = name
} }
public var description: String { public var description: String {
return "\(name)" return "\(name)"
} }
public static func ==(lhs: Identifier, rhs: Identifier) -> Bool { public static func == (lhs: Identifier, rhs: Identifier) -> Bool {
return lhs.name == rhs.name return lhs.name == rhs.name
} }
} }
/// A P4 variable
public class Variable: Identifier { public class Variable: Identifier {
var constant: Bool var constant: Bool
var value: ValueType var value: P4Value
public init(name: String, withValue value: ValueType, isConstant constant: Bool) { public init(name: String, withValue value: P4Value, isConstant constant: Bool) {
self.constant = constant self.constant = constant
self.value = value self.value = value
super.init(name: name) super.init(name: name)
} }
public override var description: String { public override var description: String {
return "\(super.description) = \(value) \(constant ? "(constant)" : "")" return "\(super.description) = \(value) \(constant ? "(constant)" : "")"
} }
public var value_type: ValueType { public var value_type: P4Value {
get { value
value }
}
}
} }
public enum ValueType: CustomStringConvertible, Equatable { /// A base for all instances of P4 types
case Boolean(Bool) open class P4ValueBase<T: P4Type>: P4Value {
case Int(Int)
case String(String)
public var description: String { public init() {}
switch self {
case ValueType.Boolean(let b):
return "\(b) of Boolean"
case ValueType.Int(let i):
return "\(i) of Int"
case ValueType.String(let s):
return "\(s) of String"
}
}
public static func==(lhs: ValueType, rhs: ValueType) -> Bool {
switch (lhs,rhs) {
case (ValueType.Boolean(let lhsb), ValueType.Boolean(let rhsb)):
return lhsb == rhsb
case (ValueType.String(let lhsb), ValueType.String(let rhsb)):
return lhsb == rhsb
case (ValueType.Int(let lhsb), ValueType.Int(let rhsb)):
return lhsb == rhsb
default: return false
}
}
public func type() -> P4Type {
return T.create()
}
public func eq(rhs: P4Value) -> Bool {
return false
}
} }
public struct Value: CustomStringConvertible { /// The type for a P4 struct
public var value_type: ValueType 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 init(withValue value: ValueType) { public init(withName name: String) {
self.value_type = value self.name = name
}
public init() {
self.name = ""
}
}
/// The field of a P4 struct
public struct P4StructField {
public let name: Identifier
public let type: P4Type
public init(withName name: Identifier, withType type: P4Type) {
self.name = name
self.type = type
}
}
/// An instance of a P4 struct
public class P4StructValue: P4ValueBase<P4Struct> {
public let fields: [P4StructField]
public init(withFields fields: [P4StructField]) {
self.fields = fields
}
}
/// A P4 boolean type
public struct P4Boolean: P4Type {
public static func create() -> any P4Type {
return P4Boolean()
}
}
/// An instance of a P4 boolean
public class P4BooleanValue: P4ValueBase<P4Boolean> {
let value: Bool
public init(withValue value: Bool) {
self.value = value
}
public override func eq(rhs: P4Value) -> Bool {
guard let bool_rhs = rhs as? P4BooleanValue else {
return false
} }
public var description: String { return self.value == bool_rhs.value
return "\(value_type)" }
}
/// A P4 int type
public struct P4Int: P4Type {
public static func create() -> any P4Type {
return P4Int()
}
}
/// An instance of a P4 integer
public class P4IntValue: P4ValueBase<P4Int> {
let value: Int
public init(withValue value: Int) {
self.value = value
}
public override func eq(rhs: P4Value) -> Bool {
guard let int_rhs = rhs as? P4IntValue else {
return false
} }
return self.value == int_rhs.value
}
}
/// A P4 string type
public struct P4String: P4Type {
public static func create() -> any P4Type {
return P4String()
}
}
/// An instance of a P4 string
public class P4StringValue: P4ValueBase<P4String> {
let value: String
public init(withValue value: String) {
self.value = value
}
public override func eq(rhs: P4Value) -> Bool {
guard let string_rhs = rhs as? P4StringValue else {
return false
}
return self.value == string_rhs.value
}
}
/// A P4 value (with a type)
public struct Value: CustomStringConvertible, Equatable {
public var type: P4Type
public var value: P4Value
public init(withValue value: P4Value, andType type: P4Type) {
self.value = value
self.type = type
}
public var description: String {
return "\(self.value) of \(self.type)"
}
public static func == (lhs: Value, rhs: Value) -> Bool {
return lhs.value.eq(rhs: rhs.value)
}
} }
public class Packet { public class Packet {
public init() {} public init() {}
} }
+9 -1
View File
@@ -20,7 +20,7 @@ public protocol EvaluatableExpression {
/// - Parameters /// - Parameters
/// - execution: The execution context in which to evaluate the expression /// - execution: The execution context in which to evaluate the expression
/// - Returns: The value of expression /// - Returns: The value of expression
func evaluate(execution: ProgramExecution) -> ValueType func evaluate(execution: ProgramExecution) -> P4Value
} }
public protocol EvaluatableParserStatement { public protocol EvaluatableParserStatement {
@@ -31,3 +31,11 @@ public protocol EvaluatableParserStatement {
func evaluate(execution: ProgramExecution) -> ProgramExecution func evaluate(execution: ProgramExecution) -> ProgramExecution
} }
public protocol P4Type {
static func create() -> P4Type
}
public protocol P4Value {
func type() -> P4Type
func eq(rhs: P4Value) -> Bool
}
+22
View File
@@ -0,0 +1,22 @@
// 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 <https://www.gnu.org/licenses/>.
import Common
public struct Instantiation {
}
+14 -6
View File
@@ -85,13 +85,21 @@ public struct ParserStates {
nonisolated(unsafe) public let accept: ParserState = ParserState(name: "accept") nonisolated(unsafe) public let accept: ParserState = ParserState(name: "accept")
nonisolated(unsafe) public let reject: ParserState = ParserState(name: "reject") nonisolated(unsafe) public let reject: ParserState = ParserState(name: "reject")
public struct Parser { public struct Parser: P4Type {
public var states: [ParserState] = Array() public var states: [ParserState] = Array()
public var count: Int { public var count: Int {
states.count states.count
} }
public init() {} public var name: Identifier
public init(withName name: Identifier) {
self.name = name
}
public static func create() -> any P4Type {
return Parser(withName: Identifier(name: ""))
}
public func findStartState() -> ParserState? { public func findStartState() -> ParserState? {
for state in states { for state in states {
@@ -103,25 +111,25 @@ public struct Parser {
} }
} }
public class ParserExecution: ProgramExecution { public class ParserInstance: ProgramExecution {
private init(state: ParserState) { private init(state: ParserState) {
self.state = state self.state = state
super.init() super.init()
} }
public static func create(_ parser: Parser) -> Result<ParserExecution> { public static func create(_ parser: Parser) -> Result<ParserInstance> {
guard let start_state = parser.findStartState() else { guard let start_state = parser.findStartState() else {
return Result.Error(Error(withMessage: "Could not find the start state")) return Result.Error(Error(withMessage: "Could not find the start state"))
} }
let new = ParserExecution(state: start_state) let new = ParserInstance(state: start_state)
return Result.Ok(new) return Result.Ok(new)
} }
public var state: ParserState public var state: ParserState
public func transition(toNextState state: ParserState) -> ParserExecution { public func transition(toNextState state: ParserState) -> ParserInstance {
let next = self let next = self
next.state = state next.state = state
return next return next
+24 -1
View File
@@ -15,7 +15,30 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common
public struct Program { public struct Program {
public var parsers: [Parser] = Array() public var types: [P4Type] = Array()
/// Find the program's main parser
///
/// Note: For now, the main parser is expected to be named main_parser.
public func starting_parser() -> Result<Parser> {
return self.find_parser(withName: Identifier(name: "main_parser"))
}
public func find_parser(withName name: Identifier) -> Result<Parser> {
for type in self.types {
print("type: \(type)")
guard let parser = type as? Parser else {
continue
}
if parser.name == name {
return .Ok(parser)
}
}
return .Error(Error(withMessage: "Could not find parser named \(name)"))
}
public init() {} public init() {}
} }
+39 -46
View File
@@ -24,45 +24,6 @@ import TreeSitterP4
let p4lang = Language(tree_sitter_p4()) let p4lang = Language(tree_sitter_p4())
public protocol ParseableValueType {
static func Parse(type: String, withValue value: String) -> Result<ValueType>
}
// This seems unnecessary because all the value types are in a single enum?
extension ValueType: ParseableValueType {
public static func Parse(type: String, withValue value: String) -> Result<ValueType> {
if type == "bool" {
// Default
if value == "" {
return .Ok(ValueType.Boolean(false))
}
if value == "true" {
return .Ok(ValueType.Boolean(true))
} else if value == "false" {
return .Ok(ValueType.Boolean(false))
}
return .Error(Error(withMessage: "Cannot convert \(value) into boolean value"))
} else if type == "string" {
return .Ok(ValueType.String(value))
} else if type == "int" {
// Default
if value == "" {
return .Ok(ValueType.Int(0))
}
guard let parsed_value = Swift.Int(value) else {
return .Error(Error(withMessage: "Cannot convert \(value) into integer value"))
}
return .Ok(ValueType.Int(parsed_value))
}
return .Error(Error(withMessage: "Invalid type"))
}
}
public protocol ParseableParserStatement { public protocol ParseableParserStatement {
static func Parse(node: Node, inTree tree: MutableTree) -> Result<EvaluatableParserStatement?> static func Parse(node: Node, inTree tree: MutableTree) -> Result<EvaluatableParserStatement?>
} }
@@ -129,7 +90,7 @@ extension VariableDeclarationStatement: ParseableParserStatement {
"" ""
} }
return switch ValueType.Parse(type: type_name, withValue: value) { return switch Parser.ParseValueType(type: type_name, withValue: value) {
case Result.Ok(let value_type): case Result.Ok(let value_type):
Result.Ok( Result.Ok(
VariableDeclarationStatement( VariableDeclarationStatement(
@@ -141,6 +102,37 @@ extension VariableDeclarationStatement: ParseableParserStatement {
} }
public struct Parser { public struct Parser {
static func ParseValueType(type: String, withValue value: String) -> Result<P4Value> {
if type == "bool" {
// Default
if value == "" {
return .Ok(P4BooleanValue(withValue: false))
}
if value == "true" {
return .Ok(P4BooleanValue(withValue: true))
} else if value == "false" {
return .Ok(P4BooleanValue(withValue: false))
}
return .Error(Error(withMessage: "Cannot convert \(value) into boolean value"))
} else if type == "string" {
return .Ok(P4StringValue.init(withValue: value))
} else if type == "int" {
// Default
if value == "" {
return .Ok(P4IntValue.init(withValue: 0))
}
guard let parsed_value = Swift.Int(value) else {
return .Error(Error(withMessage: "Cannot convert \(value) into integer value"))
}
return .Ok(P4IntValue.init(withValue: parsed_value))
}
return .Error(Error(withMessage: "Invalid type"))
}
public struct P4Parser { public struct P4Parser {
@@ -305,7 +297,7 @@ public struct Parser {
withTransition: transition_statement)) withTransition: transition_statement))
} }
} }
static func Parser(node: Node, inTree tree: MutableTree) -> Result<Lang.Parser> { static func Parser(withName name: Identifier, node: Node, inTree tree: MutableTree) -> Result<Lang.Parser> {
guard guard
let parser_state_query = try? SwiftTreeSitter.Query( let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang, language: p4lang,
@@ -317,7 +309,7 @@ public struct Parser {
Error(withMessage: "Could not compile the parser state tree sitter query")) Error(withMessage: "Could not compile the parser state tree sitter query"))
} }
var parser = Lang.Parser() var parser = Lang.Parser(withName: name)
// Build a state from each one listed. // Build a state from each one listed.
for parser_states in parser_state_query.execute(node: node, in: tree) { for parser_states in parser_state_query.execute(node: node, in: tree) {
@@ -340,7 +332,8 @@ public struct Parser {
let result = p.parse(source) let result = p.parse(source)
guard let tree = result, guard let tree = result,
!tree.isError(lang: p4lang) !tree.isError(lang: p4lang),
!tree.containsMissing(lang: p4lang)
else { else {
return Result.Error(Error(withMessage: "Could not compile the P4 program")) return Result.Error(Error(withMessage: "Could not compile the P4 program"))
} }
@@ -349,7 +342,7 @@ public struct Parser {
let parser_declaration_query = try? SwiftTreeSitter.Query( let parser_declaration_query = try? SwiftTreeSitter.Query(
language: p4lang, language: p4lang,
data: String( data: String(
"(parserDeclaration (parserType) (parserStates) @parser-states)" "(parserDeclaration (parserType parser_name: (identifier) @parser-name) (parserStates) @parser-states)"
).data(using: String.Encoding.utf8)!) ).data(using: String.Encoding.utf8)!)
else { else {
return Result.Error( return Result.Error(
@@ -361,8 +354,8 @@ public struct Parser {
let parser_qc = parser_declaration_query.execute(in: tree) let parser_qc = parser_declaration_query.execute(in: tree)
for parser_declaration in parser_qc { for parser_declaration in parser_qc {
switch Parser(node: parser_declaration.nodes[0], inTree: tree) { switch Parser(withName: Identifier(name: parser_declaration.nodes[0].text!), node: parser_declaration.nodes[1], inTree: tree) {
case Result.Ok(let parser): program.parsers.append(parser) case Result.Ok(let parser): program.types.append(parser)
case Result.Error(let error): return Result.Error(error) case Result.Error(let error): return Result.Error(error)
} }
} }
+15 -9
View File
@@ -18,33 +18,39 @@
import Common import Common
import Lang import Lang
/// The runtime for a parser
public class ParserRuntime: CustomStringConvertible { public class ParserRuntime: CustomStringConvertible {
var execution: ParserExecution var execution: ParserInstance
init(execution: ParserExecution) { init(execution: ParserInstance) {
self.execution = execution self.execution = execution
} }
public static func create(program: Lang.Parser) -> Result<ParserRuntime> { /// Create a parser runtime from a P4 program
switch ParserExecution.create(program) { public static func create(program: Lang.Program) -> Result<ParserRuntime> {
case .Ok(let execution): return .Ok(Runtime.ParserRuntime(execution: execution)) return switch program.starting_parser() {
case .Error(let error): return .Error(error) case .Ok(let parser):
switch ParserInstance.create(parser) {
case .Ok(let execution): .Ok(Runtime.ParserRuntime(execution: execution))
case .Error(let error): .Error(error)
}
case .Error(let error): .Error(error)
} }
} }
/// Run the P4 parser on a given packet
public func run(input: Packet) -> Result<(ParserState, ProgramExecution)> { public func run(input: Packet) -> Result<(ParserState, ProgramExecution)> {
execution.scopes.enter() execution.scopes.enter()
return .Ok(execution.execute()) return .Ok(execution.execute())
} }
public var description: String { public var description: String {
//return "\(super.description)\nState: \(execution?.description ?? "N/A")\nError: \(error?.description ?? "None")"
return "Runtime:\nExecution: \(execution)" return "Runtime:\nExecution: \(execution)"
} }
} }
extension ParserExecution: Execution { /// Instances of parsers are executable
extension ParserInstance: Execution {
public func execute() -> (ParserState, ProgramExecution) { public func execute() -> (ParserState, ProgramExecution) {
var execution = self as ProgramExecution var execution = self as ProgramExecution
var state = self.state var state = self.state
+17 -1
View File
@@ -35,5 +35,21 @@ extension MutableTree {
} }
return false return false
} }
} public func containsMissing(lang: Language) -> Bool {
guard
let parser_error_query = try? SwiftTreeSitter.Query(
language: lang,
data: String(
"(MISSING)"
).data(using: String.Encoding.utf8)!)
else {
return false
}
let error_qr = parser_error_query.execute(in: self)
for _ in error_qr {
return true
}
return false
}
}
+33 -14
View File
@@ -29,47 +29,66 @@ import Macros
@Test func test_simple_parser() async throws { @Test func test_simple_parser() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser simple() { parser main_parser() {
state start { state start {
transition drop; transition drop;
} }
} };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Parser.Program(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
#expect(program.parsers.count == 1) #expect(parser.states.count == 1)
#expect(program.parsers[0].states.count == 1) #expect(parser.states[0].state_name == "start")
#expect(program.parsers[0].states[0].state_name == "start") #expect(parser.states[0].statements.count == 0)
#expect(program.parsers[0].states[0].statements.count == 0)
} }
@Test func test_simple_parser_syntax_error() async throws { @Test func test_simple_parser_syntax_error() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser simple() { parser main_parser() {
state state
transition drop; transition drop;
} }
} };
""" """
#expect(#RequireErrorResult(Error(withMessage: "Could not compile the P4 program"), Parser.Program(simple_parser_declaration))) #expect(#RequireErrorResult(Error(withMessage: "Could not compile the P4 program"), Parser.Program(simple_parser_declaration)))
} }
@Test func test_simple_parser_with_statement() async throws { @Test func test_simple_parser_with_statement() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser simple() { parser main_parser() {
state start { state start {
true; true;
transition drop; transition drop;
} }
} };
"""
let program = try #UseOkResult(Parser.Program(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
#expect(parser.states.count == 1)
#expect(parser.states[0].state_name == "start")
#expect(parser.states[0].statements.count == 1)
}
@Test func test_simple_parser_with_instantiation() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
true;
transition drop;
}
};
bool() main;
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Parser.Program(simple_parser_declaration))
#expect(program.parsers.count == 1) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
#expect(program.parsers[0].states.count == 1) #expect(parser.states.count == 1)
#expect(program.parsers[0].states[0].state_name == "start") #expect(parser.states[0].state_name == "start")
#expect(program.parsers[0].states[0].statements.count == 1) #expect(parser.states[0].statements.count == 1)
} }
+58 -60
View File
@@ -15,11 +15,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import Foundation
import Runtime
import Common import Common
import Foundation
import Lang import Lang
import Macros import Macros
import Runtime
import SwiftTreeSitter import SwiftTreeSitter
import Testing import Testing
import TreeSitter import TreeSitter
@@ -28,77 +28,75 @@ import TreeSitterP4
@testable import Parser @testable import Parser
@Test func test_simple_runtime() async throws { @Test func test_simple_runtime() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser simple() { parser main_parser() {
state start { state start {
true; true;
transition reject; transition reject;
} }
} };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Parser.Program(simple_parser_declaration))
#expect(#RequireOkResult(Runtime.ParserRuntime.create(program: program.parsers[0]))) #expect(#RequireOkResult(Runtime.ParserRuntime.create(program: program)))
} }
@Test func test_simple_runtime_no_start_state() async throws { @Test func test_simple_runtime_no_start_state() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser simple() { parser main_parser() {
state tart { state tart {
true; true;
transition reject; transition reject;
} }
} };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Parser.Program(simple_parser_declaration))
#expect( #expect(
#RequireErrorResult<ParserRuntime>( #RequireErrorResult<ParserRuntime>(
Error(withMessage: "Could not find the start state"), Error(withMessage: "Could not find the start state"),
Runtime.ParserRuntime.create(program: program.parsers[0]))) Runtime.ParserRuntime.create(program: program)))
} }
@Test func test_simple_local_element_variable_declaration() async throws { @Test func test_simple_local_element_variable_declaration() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser simple() { parser main_parser() {
state start { state start {
bool b = false; bool b = false;
string s = "testing"; string s = "testing";
true; true;
false; false;
transition reject; transition reject;
} }
} };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Parser.Program(simple_parser_declaration))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program.parsers[0])) let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program))
// This seems awkward to me! // This seems awkward to me!
// TODO: Is there a better way? // TODO: Is there a better way?
guard case Common.Result.Ok(let (state_result, execution_result)) = runtime.run(input: Packet()) else { guard case Common.Result.Ok(let (state_result, execution_result)) = runtime.run(input: Packet())
assert(false) else {
} assert(false)
}
// There should be 1 scope. // There should be 1 scope.
#expect(execution_result.scopes.count == 1) #expect(execution_result.scopes.count == 1)
guard let scope = execution_result.scopes.current else { guard let scope = execution_result.scopes.current else {
assert(false) assert(false)
} }
// We should be in the accept state.
#expect(state_result == Lang.accept)
// There are two variables declared.
#expect(scope.count == 2)
// Check the names/values of the variables in scope.
let b = try #require(scope.lookup(identifier: Identifier(name: "b")))
let s = try #require(scope.lookup(identifier: Identifier(name: "s")))
#expect(b.value_type == ValueType.Boolean(false))
#expect(s.value_type == ValueType.String("\"testing\""))
// We should be in the accept state.
#expect(state_result == Lang.accept)
// There are two variables declared.
#expect(scope.count == 2)
// Check the names/values of the variables in scope.
let b = try #require(scope.lookup(identifier: Identifier(name: "b")))
let s = try #require(scope.lookup(identifier: Identifier(name: "s")))
#expect(b.value_type.eq(rhs: P4BooleanValue(withValue: false)))
#expect(s.value_type.eq(rhs: P4StringValue(withValue: "\"testing\"")))
} }