parser, compiler: Support Table Actions
Continuous Integration / Grammar Tests (push) Has been cancelled
Continuous Integration / Library Tests (push) Has been cancelled
Continuous Integration / Library Format Tests (push) Has been cancelled

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-30 05:38:29 -04:00
parent 833979a5c9
commit b97aa1af72
6 changed files with 342 additions and 17 deletions
+75 -7
View File
@@ -459,6 +459,10 @@ extension Control: CompilableDeclaration {
} }
actions.append(action_declaration) actions.append(action_declaration)
local_context = updated_context local_context = updated_context
// Now, add the declaration into the context.
local_context = local_context.update(
newTypes: local_context.types.declare(
identifier: action_declaration.name, withValue: action_declaration))
} else if current_node.nodeType == "table_declaration" { } else if current_node.nodeType == "table_declaration" {
let maybe_table_declaration = Table.Compile( let maybe_table_declaration = Table.Compile(
node: current_node, withContext: local_context) node: current_node, withContext: local_context)
@@ -721,6 +725,58 @@ extension TableKeys: Compilable {
} }
} }
extension TableActionsProperty: Compilable {
public typealias T = TableActionsProperty
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(TableActionsProperty, CompilerContext)> {
#RequireNodeType<Node, (TableActionsProperty, CompilerContext)>(
node: node, type: "table_actions", nice_type_name: "Table Actions")
var walker = Walker(node: node)
// Skip the
// actions = {
// 1 2 3
walker.next() // 1
walker.next() // 2
walker.next() // 3
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(TableActionsProperty, CompilerContext)>.Error(
ErrorOnNode(
node: node, withError: "Missing table actions declaration component in control declaration"))
)
let (actions, errors) = walker.try_map(n: node.childCount - 1, onlyNamed: true) { current_node in
switch Identifier.Compile(node: current_node, withContext: context) {
case .Ok(let listed_action):
switch context.types.lookup(identifier: listed_action) {
case .Ok(let maybe_action):
if maybe_action.eq(rhs: Action()) {
return .Ok(TypedIdentifier(id: listed_action, withType: P4Type(maybe_action)))
}
return .Error(
ErrorOnNode(node: node, withError: "\(listed_action) does not name an action"))
case .Error(let e): return .Error(e)
}
case .Error(let e): return .Error(e)
}
}
if !errors.isEmpty {
return .Error(
ErrorOnNode(node: node, withError: "Error(s) parsing table actions: "
+ (errors.map { error in
return "\(error.msg)"
}.joined(separator: ";"))))
}
return .Ok((TableActionsProperty(actions), context))
}
}
extension TablePropertyList: Compilable { extension TablePropertyList: Compilable {
public typealias T = TablePropertyList public typealias T = TablePropertyList
public static func Compile( public static func Compile(
@@ -733,7 +789,7 @@ extension TablePropertyList: Compilable {
var current_context = context var current_context = context
var keys: [TableKeys] = Array() var keys: [TableKeys] = Array()
var _: [Action] = Array() // Actions are not yet supported var actions: [TableActionsProperty] = Array()
var errors: [Error] = Array() var errors: [Error] = Array()
node.enumerateNamedChildren { child in node.enumerateNamedChildren { child in
@@ -745,9 +801,12 @@ extension TablePropertyList: Compilable {
case .Error(let e): errors.append(e) case .Error(let e): errors.append(e)
} }
} else if child.nodeType == "table_actions" { } else if child.nodeType == "table_actions" {
errors.append( switch TableActionsProperty.Compile(node: child, withContext: current_context) {
ErrorOnNode( case .Ok((let table_action_property, let updated_context)):
node: child, withError: "Actions in table property lists are not yet supported")) current_context = updated_context
actions.append(table_action_property)
case .Error(let e): errors.append(e)
}
} else { } else {
errors.append( errors.append(
ErrorOnNode(node: child, withError: "Uknown node type in control declaration")) ErrorOnNode(node: child, withError: "Uknown node type in control declaration"))
@@ -756,8 +815,7 @@ extension TablePropertyList: Compilable {
if !errors.isEmpty { if !errors.isEmpty {
return .Error( return .Error(
Error( ErrorOnNode(node: node, withError: "Error(s) parsing property list: "
withMessage: "Error(s) parsing property list: "
+ (errors.map { error in + (errors.map { error in
return "\(error.msg)" return "\(error.msg)"
}.joined(separator: ";")))) }.joined(separator: ";"))))
@@ -770,7 +828,17 @@ extension TablePropertyList: Compilable {
ErrorOnNode(node: node, withError: "More than one key set in table property list")) ErrorOnNode(node: node, withError: "More than one key set in table property list"))
} }
return .Ok((TablePropertyList(withActions: TableActions(), withKeys: keys[0]), current_context)) // There should be only one table keys!
if actions.count > 1 {
// Todo: Make this error message better.
return .Error(
ErrorOnNode(node: node, withError: "More than one actions in table property list"))
}
if actions.isEmpty {
actions.append(TableActionsProperty())
}
return .Ok((TablePropertyList(withActions: actions[0], withKeys: keys[0]), current_context))
} }
} }
+17
View File
@@ -51,4 +51,21 @@ public struct Walker {
} }
return Result.Ok(()) return Result.Ok(())
} }
public func try_map<T>(n: Int, onlyNamed: Bool = false, todo: (Node) -> Result<T>) -> ([T], [Error]) {
var errors: [Error] = Array()
var results: [T] = Array()
for currentChildIdx in currentChildIdx..<n {
let currentChild = node.child(at: currentChildIdx)!
if onlyNamed && !currentChild.isNamed {
continue
}
switch todo(currentChild) {
case .Ok(let r): results.append(r)
case .Error(let e): errors.append(e)
}
}
return (results, errors)
}
} }
+69 -9
View File
@@ -17,7 +17,57 @@
import Common import Common
public struct Action: CustomStringConvertible { public struct Action: CustomStringConvertible, P4DataType, P4DataValue {
public func type() -> any Common.P4DataType {
return self
}
public func eq(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let arhs as Action: self.name == arhs.name
default: false
}
}
public func eq(rhs: any Common.P4DataType) -> Bool {
return switch rhs {
case is Action: true
default: false
}
}
public func lt(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name < arhs.name
default: return false
}
}
public func lte(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name <= arhs.name
default: return false
}
}
public func gt(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name > arhs.name
default: return false
}
}
public func gte(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name >= arhs.name
default: return false
}
}
public func def() -> any Common.P4DataValue {
return Action()
}
public var description: String { public var description: String {
return "Action: " return "Action: "
+ "\(self.name) with parameters \(self.params) and body \(String(describing: self.body))" + "\(self.name) with parameters \(self.params) and body \(String(describing: self.body))"
@@ -28,8 +78,8 @@ public struct Action: CustomStringConvertible {
public var name: Identifier public var name: Identifier
public init( public init(
named name: Identifier, withParameters parameters: ParameterList, named name: Identifier = Identifier(name: ""), withParameters parameters: ParameterList = ParameterList([]),
withBody body: BlockStatement? withBody body: BlockStatement? = .none
) { ) {
self.name = name self.name = name
self.params = parameters self.params = parameters
@@ -88,15 +138,24 @@ public struct TableKeys: CustomStringConvertible {
} }
} }
/// TODO public struct TableActionsProperty: CustomStringConvertible {
public struct TableActions { public let actions: [TypedIdentifier]
public init() {} public init(_ actions: [TypedIdentifier] = []) {
self.actions = actions
}
public var description: String {
return "Table Actions: "
+ self.actions.map { action in
return action.description
}.joined(separator: ";")
}
} }
public struct TablePropertyList: CustomStringConvertible { public struct TablePropertyList: CustomStringConvertible {
let actions: TableActions let actions: TableActionsProperty
let keys: TableKeys let keys: TableKeys
public init(withActions actions: TableActions, withKeys keys: TableKeys) { public init(withActions actions: TableActionsProperty, withKeys keys: TableKeys) {
self.actions = actions self.actions = actions
self.keys = keys self.keys = keys
} }
@@ -208,7 +267,8 @@ public struct Control: P4DataType, P4DataValue, Equatable, CustomStringConvertib
withParameters: ParameterList(), withParameters: ParameterList(),
withTable: Table( withTable: Table(
withName: Identifier(name: "empty"), withName: Identifier(name: "empty"),
withPropertyList: TablePropertyList(withActions: TableActions(), withKeys: TableKeys())), withPropertyList: TablePropertyList(
withActions: TableActionsProperty(), withKeys: TableKeys())),
withActions: Actions(withActions: []), withApply: ApplyStatement()) withActions: Actions(withActions: []), withApply: ApplyStatement())
} }
+107
View File
@@ -89,6 +89,113 @@ import P4Lang
#expect(program.InstancesWithTypes(filter).count == 2) #expect(program.InstancesWithTypes(filter).count == 2)
} }
@Test func test_simple_control_declaration_with_actions() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
a;
}
}
apply {
}
};
"""
let x = { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(x).count == 1)
}
@Test func test_simple_control_declaration_with_misnamed_actions() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
b;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{54, 63}: Error(s) parsing property list: {91, 26}: Error(s) parsing table actions: Cannot find b in lexical scope."
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_misnamed_actions2() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{54, 72}: Error(s) parsing property list: {91, 35}: Error(s) parsing table actions: Cannot find b in lexical scope."
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_mistyped_actions() async throws {
let simple_parser_declaration = """
bool a() {
return true;
};
control simple() {
table t {
key = {
true: exact;
}
actions = {
a;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{64, 63}: Error(s) parsing property list: {101, 26}: Error(s) parsing table actions: {101, 26}: a does not name an action"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_parameters() async throws { @Test func test_simple_control_declaration_with_parameters() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
control simple(bool x, bool y) { control simple(bool x, bool y) {
+1 -1
View File
@@ -94,7 +94,7 @@ export default grammar({
table_property_list: $ => repeat1(choice($.table_keys, $.table_actions)), table_property_list: $ => repeat1(choice($.table_keys, $.table_actions)),
table_keys: $=> seq($.key, '=', '{', repeat($.table_key_entry), '}'), table_keys: $=> seq($.key, '=', '{', repeat($.table_key_entry), '}'),
table_key_entry: $=> seq($.keysetExpression, ':', $.table_key_match_type, $._semicolon), table_key_entry: $=> seq($.keysetExpression, ':', $.table_key_match_type, $._semicolon),
table_actions: $=> seq($.actions, '=', '{', optional(repeat1($.identifier)), '}'), table_actions: $=> seq($.actions, '=', '{', optional(repeat1(seq($.identifier, $._semicolon))), '}'),
// match types // match types
table_key_match_type: $ => choice($.exact), // support exact only for now. table_key_match_type: $ => choice($.exact), // support exact only for now.
+73
View File
@@ -54,4 +54,77 @@ control simple() {
) )
) )
) )
=========================
Simple Control Declaration With Actions
=========================
control simple() {
action a() {
}
action b() {
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
---
(p4program
(declaration
(control_declaration
(control)
(identifier)
(parameters)
(action_declaration
(action)
(identifier)
(parameters)
(blockStatement)
)
(action_declaration
(action)
(identifier)
(parameters)
(blockStatement)
)
(table_declaration
(table)
(identifier)
(table_property_list
(table_keys
(key)
(table_key_entry
(keysetExpression
(expression
(simple_expression
(identifier)
)
)
)
(table_key_match_type
(exact)
)
)
)
(table_actions
(actions)
(identifier)
(identifier)
)
)
)
(apply_statement
(apply)
(blockStatement)
)
)
)
)