diff --git a/Sources/P4Compiler/Declarations.swift b/Sources/P4Compiler/Declarations.swift
index 9540ba4..d5286e8 100644
--- a/Sources/P4Compiler/Declarations.swift
+++ b/Sources/P4Compiler/Declarations.swift
@@ -691,7 +691,6 @@ extension TableKeys: Compilable {
walker.next() // 3
var current_node: Node? = .none
- var current_context = context
#MustOr(
result: current_node, thing: walker.getNext(),
@@ -700,28 +699,22 @@ extension TableKeys: Compilable {
node: node, withError: "Missing table keys declaration component in control declaration"))
)
- var entries: [TableKeyEntry] = Array()
- var errors: [Error] = Array()
-
- current_node!.enumerateNamedChildren { entry in
- switch TableKeyEntry.Compile(node: current_node!, withContext: current_context) {
- case .Ok((let keyset_expression, let updated_context)):
- entries.append(keyset_expression)
- current_context = updated_context
- case .Error(let e): errors.append(e)
+ let (keys, errors) = walker.try_map(n: node.childCount - 1, onlyNamed: true) { current_node in
+ return switch TableKeyEntry.Compile(node: current_node, withContext: context) {
+ case .Ok((let keyset_expression, _)): .Ok(keyset_expression)
+ case .Error(let e): .Error(e)
}
}
if !errors.isEmpty {
return .Error(
- Error(
- withMessage: "Error(s) parsing table key: "
+ ErrorOnNode(node: node, withError: "Error(s) parsing table key: "
+ (errors.map { error in
return "\(error.msg)"
}.joined(separator: ";"))))
}
- return .Ok((TableKeys(withEntries: entries), current_context))
+ return .Ok((TableKeys(withEntries: keys), context))
}
}
@@ -828,7 +821,7 @@ extension TablePropertyList: Compilable {
ErrorOnNode(node: node, withError: "More than one key set in table property list"))
}
- // There should be only one table keys!
+ // There should be only one table actions!
if actions.count > 1 {
// Todo: Make this error message better.
return .Error(
diff --git a/Sources/P4Lang/Control.swift b/Sources/P4Lang/Control.swift
index 9870ceb..dcbcd5b 100644
--- a/Sources/P4Lang/Control.swift
+++ b/Sources/P4Lang/Control.swift
@@ -89,7 +89,7 @@ public struct Action: CustomStringConvertible, P4DataType, P4DataValue {
}
public struct Actions: CustomStringConvertible {
- let actions: [Action]
+ public let actions: [Action]
public init(withActions actions: [Action]) {
self.actions = actions
}
@@ -107,8 +107,8 @@ public enum TableKeyMatchType {
}
public struct TableKeyEntry: CustomStringConvertible {
- let key: KeysetExpression
- let match_type: TableKeyMatchType
+ public let key: KeysetExpression
+ public let match_type: TableKeyMatchType
public init(_ key: KeysetExpression, _ match: TableKeyMatchType) {
self.key = key
@@ -121,19 +121,19 @@ public struct TableKeyEntry: CustomStringConvertible {
}
public struct TableKeys: CustomStringConvertible {
- let entries: [TableKeyEntry]
+ public let keys: [TableKeyEntry]
public init(withEntries entries: [TableKeyEntry]) {
- self.entries = entries
+ self.keys = entries
}
public init() {
- self.entries = []
+ self.keys = []
}
public var description: String {
return "Table Keys: "
- + self.entries.map { entry in
- return "\(entry)"
+ + self.keys.map { key in
+ return "\(key)"
}.joined(separator: ";")
}
}
@@ -153,8 +153,8 @@ public struct TableActionsProperty: CustomStringConvertible {
}
public struct TablePropertyList: CustomStringConvertible {
- let actions: TableActionsProperty
- let keys: TableKeys
+ public let actions: TableActionsProperty
+ public let keys: TableKeys
public init(withActions actions: TableActionsProperty, withKeys keys: TableKeys) {
self.actions = actions
self.keys = keys
@@ -166,17 +166,30 @@ public struct TablePropertyList: CustomStringConvertible {
}
public struct Table: CustomStringConvertible {
- let properties: TablePropertyList
+ public let properties: TablePropertyList
let name: Identifier
+ public let entries: [(P4Value, TypedIdentifier)]
- public init(withName name: Identifier, withPropertyList property_list: TablePropertyList) {
+ public init(
+ withName name: Identifier, withPropertyList property_list: TablePropertyList,
+ withEntries entries: [(P4Value, TypedIdentifier)] = []
+ ) {
self.name = name
self.properties = property_list
+ self.entries = entries
}
public var description: String {
return "Table named: \(self.name) \(self.properties)"
}
+
+ /// When the control is evaluated, the value of the x in the table is
+ /// compared to the entries and the match is assocated with an action
+ /// that is invoked when the match occurs!
+
+ public func update(addEntry entry: (P4Value, TypedIdentifier)) -> Table{
+ return Table(withName: self.name, withPropertyList: self.properties, withEntries: self.entries + [entry])
+ }
}
public struct Control: P4DataType, P4DataValue, Equatable, CustomStringConvertible {
@@ -236,8 +249,8 @@ public struct Control: P4DataType, P4DataValue, Equatable, CustomStringConvertib
return "Control named \(self._name) \(self.parameters) \(self.actions) \(self.table)"
}
- let actions: Actions
- let table: Table
+ public let actions: Actions
+ public let table: Table
let _parameters: ParameterList
let _name: Identifier
let apply: ApplyStatement
@@ -261,6 +274,12 @@ public struct Control: P4DataType, P4DataValue, Equatable, CustomStringConvertib
self.apply = apply
}
+ public func updateTable(addEntry entry: (P4Value, TypedIdentifier)) -> Control {
+ let table = self.table.update(addEntry: entry)
+
+ return Control(named: self.name, withParameters: self.parameters, withTable: table, withActions: self.actions, withApply: self.apply)
+ }
+
public func def() -> any P4DataValue {
return Control(
named: Identifier(name: ""),
diff --git a/Sources/P4Runtime/Control.swift b/Sources/P4Runtime/Control.swift
new file mode 100644
index 0000000..b1b2006
--- /dev/null
+++ b/Sources/P4Runtime/Control.swift
@@ -0,0 +1,100 @@
+// 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 Common
+import P4Lang
+
+extension Control: LibraryCallable {
+ public typealias T = P4TableHitMissValue
+ public func call(
+ execution: Common.ProgramExecution, arguments: ArgumentList
+ ) -> (P4TableHitMissValue, Common.ProgramExecution) {
+
+ var control_execution = execution.enter_scope()
+
+ // Add initial values to the global scope
+ for (name, value) in execution.getGlobalValues() {
+ control_execution = control_execution.declare(identifier: name, withValue: value)
+ }
+
+ let call_body: (ProgramExecution) -> (Result, ProgramExecution) = {
+ execution in
+ var control_execution = execution
+
+ for action in self.actions.actions {
+ control_execution = control_execution.declare(
+ identifier: action.name, withValue: P4Value(action))
+ }
+
+ for key in self.table.properties.keys.keys {
+ // Every evaluation of the key starts from an unchanged execution context.
+ let (key_eval, updated_execution) = key.key.evaluate(execution: control_execution)
+
+ guard case .Ok(let key_val) = key_eval else {
+ return (.Error(key_eval.error()!), updated_execution)
+ }
+
+ /// ASSUME: The first matching entry is the one to do.
+ /// TODO: Check whether this matches architecture.
+ for (val, action) in self.table.entries {
+
+ // Skip those with mismatching types.
+
+ if !val.type().eq(key_val.type()) {
+ continue;
+ }
+
+ /// ASSUME: All matches are exact.
+ if val.eq(key_val) {
+ // Lookup action!
+
+ let maybe_action = updated_execution.scopes.lookup(identifier: action)
+ guard case .Ok(let action) = maybe_action else {
+ return (.Error(maybe_action.error()!), updated_execution)
+ }
+
+ let aaction = (action.dataValue() as! Action)
+
+ return switch aaction.evaluate(execution: updated_execution) {
+ case (ControlFlow.Error, let updated_execution):
+ (.Error(updated_execution.getError()!), updated_execution)
+ case (_, let updated_execution): (.Ok(P4TableHitMissValue.Hit), updated_execution)
+ }
+ }
+ }
+ }
+ return (.Ok(P4TableHitMissValue.Miss), control_execution)
+ }
+
+ switch Call(body: call_body, withArguments: arguments, withParameters: self.parameters, inExecution: control_execution) {
+ case (.Ok(let r), let updated_execution): return (r, updated_execution)
+ case (.Error(let e), let updated_execution): return (P4TableHitMissValue.Miss, updated_execution.setError(error: e))
+ }
+ }
+}
+
+extension Action: EvaluatableStatement {
+ public func evaluate(
+ execution: Common.ProgramExecution
+ ) -> (Common.ControlFlow, Common.ProgramExecution) {
+ if let body = self.body {
+ return body.evaluate(execution: execution)
+ }
+
+ return (ControlFlow.Next, execution)
+ }
+}
diff --git a/Tests/p4rseTests/ControlCompilerTests.swift b/Tests/p4rseTests/ControlTests/Compile.swift
similarity index 100%
rename from Tests/p4rseTests/ControlCompilerTests.swift
rename to Tests/p4rseTests/ControlTests/Compile.swift
diff --git a/Tests/p4rseTests/ControlTests/Runtime.swift b/Tests/p4rseTests/ControlTests/Runtime.swift
new file mode 100644
index 0000000..d861005
--- /dev/null
+++ b/Tests/p4rseTests/ControlTests/Runtime.swift
@@ -0,0 +1,384 @@
+// 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 Common
+import Foundation
+import Macros
+import P4Lang
+import P4Runtime
+import SwiftTreeSitter
+import Testing
+import TreeSitter
+import TreeSitterP4
+
+@testable import P4Compiler
+
+@Test func test_control_single_key() async throws {
+ let simple_parser_declaration = """
+ control simple(inout int result, bool x) {
+ action a() {
+ result = 5;
+ }
+ action b() {
+ result = 7;
+ }
+ table t {
+ key = {
+ x: exact;
+ }
+ actions = {
+ a;
+ b;
+ }
+ }
+ apply {
+ }
+ };
+ """
+
+ let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
+
+ // Pull the control out of the compiled program.
+ let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
+ switch tipe.dataType() {
+ case let c as Control: c.name == "simple"
+ default: false
+ }
+ }
+ var control = ((controls[0].dataType() as P4DataType) as! Control)
+
+ // Add entries to the table.
+ control = control.updateTable(
+ addEntry: (
+ P4Value(P4BooleanValue(withValue: true)),
+ TypedIdentifier(name: "a", withType: P4Type(Action()))
+ )
+ ).updateTable(
+ addEntry: (
+ P4Value(P4BooleanValue(withValue: false)),
+ TypedIdentifier(name: "b", withType: P4Type(Action()))
+ ))
+
+ // Set a variable in the global scope for the inout first parameter.
+ var global_values = VarValueScopes().enter()
+ global_values = global_values.declare(
+ identifier: Identifier(name: "result_arg"),
+ withValue: P4Value(
+ P4IntValue(withValue: 0),
+ P4Type(P4Int())))
+
+ let runtime = try #UseOkResult(
+ P4Runtime.Runtime.create(control: control, withGlobalValues: global_values))
+
+ let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
+ withArguments: ArgumentList([
+ Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
+ Argument(P4Value(P4BooleanValue(withValue: true)), atIndex: 1),
+ ])))
+
+ // We expect there to be a hit.
+ #expect(hit_miss == P4TableHitMissValue.Hit)
+
+ // And that the proper action was invoked.
+ let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
+ #expect(result_arg.eq(P4Value(P4IntValue(withValue: 5))))
+}
+
+@Test func test_control_single_key_false() async throws {
+ let simple_parser_declaration = """
+ control simple(inout int result, bool x) {
+ action a() {
+ result = 5;
+ }
+ action b() {
+ result = 7;
+ }
+ table t {
+ key = {
+ x: exact;
+ }
+ actions = {
+ a;
+ b;
+ }
+ }
+ apply {
+ }
+ };
+ """
+
+ let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
+
+ // Pull the control out of the compiled program.
+ let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
+ switch tipe.dataType() {
+ case let c as Control: c.name == "simple"
+ default: false
+ }
+ }
+ var control = ((controls[0].dataType() as P4DataType) as! Control)
+
+ // Add entries to the table.
+ control = control.updateTable(
+ addEntry: (
+ P4Value(P4BooleanValue(withValue: true)),
+ TypedIdentifier(name: "a", withType: P4Type(Action()))
+ )
+ ).updateTable(
+ addEntry: (
+ P4Value(P4BooleanValue(withValue: false)),
+ TypedIdentifier(name: "b", withType: P4Type(Action()))
+ ))
+
+ // Set a variable in the global scope for the inout first parameter.
+ var global_values = VarValueScopes().enter()
+ global_values = global_values.declare(
+ identifier: Identifier(name: "result_arg"),
+ withValue: P4Value(
+ P4IntValue(withValue: 0),
+ P4Type(P4Int())))
+
+ let runtime = try #UseOkResult(
+ P4Runtime.Runtime.create(control: control, withGlobalValues: global_values))
+
+ let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
+ withArguments: ArgumentList([
+ Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
+ Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1),
+ ])))
+
+ // We expect there to be a hit.
+ #expect(hit_miss == P4TableHitMissValue.Hit)
+
+ // And that the proper action was invoked.
+ let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
+ #expect(result_arg.eq(P4Value(P4IntValue(withValue: 7))))
+}
+
+@Test func test_control_single_integer_key_hit() async throws {
+ let simple_parser_declaration = """
+ control simple(inout int result, int x) {
+ action a() {
+ result = 5;
+ }
+ action b() {
+ result = 7;
+ }
+ table t {
+ key = {
+ x: exact;
+ }
+ actions = {
+ a;
+ b;
+ }
+ }
+ apply {
+ }
+ };
+ """
+
+ let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
+
+ // Pull the control out of the compiled program.
+ let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
+ switch tipe.dataType() {
+ case let c as Control: c.name == "simple"
+ default: false
+ }
+ }
+ var control = ((controls[0].dataType() as P4DataType) as! Control)
+
+ // Add entries to the table.
+ control = control.updateTable(
+ addEntry: (
+ P4Value(P4IntValue(withValue: 5)),
+ TypedIdentifier(name: "a", withType: P4Type(Action()))
+ )
+ ).updateTable(
+ addEntry: (
+ P4Value(P4IntValue(withValue: 2)),
+ TypedIdentifier(name: "b", withType: P4Type(Action()))
+ ))
+
+ // Set a variable in the global scope for the inout first parameter.
+ var global_values = VarValueScopes().enter()
+ global_values = global_values.declare(
+ identifier: Identifier(name: "result_arg"),
+ withValue: P4Value(
+ P4IntValue(withValue: 0),
+ P4Type(P4Int())))
+
+ let runtime = try #UseOkResult(
+ P4Runtime.Runtime.create(control: control, withGlobalValues: global_values))
+
+ let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
+ withArguments: ArgumentList([
+ Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
+ Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 1),
+ ])))
+
+ // We expect there to be a hit.
+ #expect(hit_miss == P4TableHitMissValue.Hit)
+
+ let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
+ #expect(result_arg.eq(P4Value(P4IntValue(withValue: 5))))
+}
+
+@Test func test_control_single_integer_key_miss() async throws {
+ let simple_parser_declaration = """
+ control simple(inout int result, int x) {
+ action a() {
+ result = 5;
+ }
+ action b() {
+ result = 7;
+ }
+ table t {
+ key = {
+ x: exact;
+ }
+ actions = {
+ a;
+ b;
+ }
+ }
+ apply {
+ }
+ };
+ """
+
+ let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
+
+ // Pull the control out of the compiled program.
+ let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
+ switch tipe.dataType() {
+ case let c as Control: c.name == "simple"
+ default: false
+ }
+ }
+ var control = ((controls[0].dataType() as P4DataType) as! Control)
+
+ // Add entries to the table.
+ control = control.updateTable(
+ addEntry: (
+ P4Value(P4IntValue(withValue: 1)),
+ TypedIdentifier(name: "a", withType: P4Type(Action()))
+ )
+ ).updateTable(
+ addEntry: (
+ P4Value(P4IntValue(withValue: 2)),
+ TypedIdentifier(name: "b", withType: P4Type(Action()))
+ ))
+
+ // Set a variable in the global scope for the inout first parameter.
+ var global_values = VarValueScopes().enter()
+ global_values = global_values.declare(
+ identifier: Identifier(name: "result_arg"),
+ withValue: P4Value(
+ P4IntValue(withValue: 0),
+ P4Type(P4Int())))
+
+ let runtime = try #UseOkResult(
+ P4Runtime.Runtime.create(control: control, withGlobalValues: global_values))
+
+ let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
+ withArguments: ArgumentList([
+ Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
+ Argument(P4Value(P4IntValue(withValue: 3)), atIndex: 1),
+ ])))
+
+ // We expect there to be a hit.
+ #expect(hit_miss == P4TableHitMissValue.Miss)
+
+ let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
+ #expect(result_arg.eq(P4Value(P4IntValue(withValue: 0))))
+}
+
+@Test func test_control_multiple_keys() async throws {
+ let simple_parser_declaration = """
+ control simple(inout int result, bool x, int f) {
+ action a() {
+ result = 5;
+ }
+ action b() {
+ result = 7;
+ }
+ table t {
+ key = {
+ x: exact;
+ f: exact;
+ }
+ actions = {
+ a;
+ b;
+ }
+ }
+ apply {
+ }
+ };
+ """
+
+ let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
+
+ // Pull the control out of the compiled program.
+ let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
+ switch tipe.dataType() {
+ case let c as Control: c.name == "simple"
+ default: false
+ }
+ }
+ var control = ((controls[0].dataType() as P4DataType) as! Control)
+
+ // Add entries to the table.
+ control = control.updateTable(
+ addEntry: (
+ P4Value(P4BooleanValue(withValue: true)),
+ TypedIdentifier(name: "a", withType: P4Type(Action()))
+ )
+ ).updateTable(
+ addEntry: (
+ P4Value(P4IntValue(withValue: 5)),
+ TypedIdentifier(name: "b", withType: P4Type(Action()))
+ ))
+
+ // Set a variable in the global scope for the inout first parameter.
+ var global_values = VarValueScopes().enter()
+ global_values = global_values.declare(
+ identifier: Identifier(name: "result_arg"),
+ withValue: P4Value(
+ P4IntValue(withValue: 0),
+ P4Type(P4Int())))
+
+ let runtime = try #UseOkResult(
+ P4Runtime.Runtime.create(control: control, withGlobalValues: global_values))
+
+ let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
+ withArguments: ArgumentList([
+ Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
+ Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1),
+ Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 2),
+ ])))
+
+ // We expect there to be a hit.
+ #expect(hit_miss == P4TableHitMissValue.Hit)
+
+ // And that the proper action was invoked.
+ let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
+ #expect(result_arg.eq(P4Value(P4IntValue(withValue: 7))))
+
+}